{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Jamstatic",
  "home_page_url": "https://jamstatic.fr/",
  "feed_url": "https://jamstatic.fr/feed.json",
  "description": "Jamstatic, la communauté des sites statiques et des architectures découplées.",
  "icon": "https://jamstatic.fr/favicon.7713e34cae9563a23ca4ae37c39f46d6.png",
  "favicon": "https://jamstatic.fr/thumbnails/64x/favicon.7713e34cae9563a23ca4ae37c39f46d6.png",
  "language": "fr",
  "items": [
    {
      "id": "https://jamstatic.fr/2023/08/28/migration-site-cecil/",
      "url": "https://jamstatic.fr/2023/08/28/migration-site-cecil/",
      "title": "Pourquoi et comment j’ai migré Jamstatic.fr de Hugo vers Cecil",
      "summary": "Retour d’expérience sur la migration du site vers Cecil.",
      "date_published": "2022-08-28T00:00:00+00:00",
      "date_modified": "2026-01-28T00:00:00+00:00","content_text": "À la fin de l’année dernière j’avais entrepris de pérenniser le travail de refonte du site, engagé avec Frank : nouveau logo et nouvelle charte graphique, impliquant la modification des templates et de la feuille de styles.\nC’est la version sur laquelle vous lisez cette article 😊\nPour rappel, avant le site ressemblait à ça :\n\n\n\n\n\n\nCapture d’écran de la v1 de Jamstatic.fr\n\nPourquoi changer de solution ?\nAu tout début je m’étais concentré sur la modification des feuilles de styles en m’appuyant sur Tailwind CSS (comme souhaité par Frank), puis sur la modification des templates Go.\nPuis, 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.\nBref, une perte de temps importante et une motivation se réduisant de jour en jour.\nJ’ai alors décidé de sauter le pas et de migrer vers Cecil, pour 2 raisons majeures :\n\nJe connais la solution sur le bout des doigts (et pour cause puisque j’en suis le créateur ^^) ;\nEt surtout je suis à l’aise, et donc efficace, avec le moteur de template Twig.\n\nComment ?\nAprès le \"pourquoi ?\" intéressons nous maintenant à la partie la plus intéressante de ce billet, à savoir le \"comment ?\" 😊\nLe 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 :\n\nJ’effectue une modification ;\nJe lance un nouveau build ;\nJ’effectue les ajustements nécessaires (en m’appuyant sur les messages d’erreur retournés) ;\nJe recommence jusqu’à ce que le build soit valide.\n\nGestion de contenu\nNous pouvons séparer les contenus en 2 catégories :\n\nLes pages, c’est à dire les articles rédigés dans le format Markdown\nLes assets, c’est à dire les illustrations et autres vidéos au sein des articles\n\nAussi, je me suis tout d’abord concentré sur la réorganisation de ces contenus :\n\nDéplacement des fichiers *.md du dossier content\/ de Hugo vers le dossier pages\/ de Cecil\nRenommage des fichiers dans la section post de manière à les préfixer avec la date de l’article (YYYY-MM-DD_Titre.md) et ainsi faciliter leur tri\nDéplacement des fichiers médias dans le dossier dédié de Cecil (asset\/)\n\nMise en forme et templates\nLe changement de moteur de templates aura certainement été le plus gros du travail, mais qui aura permis d’optimiser le rendu, via :\n\nLa rationalisation et la factorisation des templates de manière à ne pas\/plus dupliquer du code\nLa suppression des templates inutiles, c’est à dire disponibles en natif avec Cecil (tel que celui du flux RSS)\n\nDépendances et scripts\nCecil 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 package.json au strict minimum : à savoir le support de Tailwind CSS et de All Contributors.\nDe plus, il existe des Themes Components pour Cecil, c’est à dire des thèmes utilitaires, dont PWA permettant de transformer n’importe quel site web généré avec Cecil en Progessive Web App.\nImage de partage\nL’image de partage sur les réseaux sociaux était créée de manière semi-automatique via l’API HTTP de manipulation d’image de Cloudinary, en paçant le lien (fabriqué à la main) dans le front matter de chaque post.\nMê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.\nDe plus le service de Cloudinary était sollicité à chaque affichage du post concerné, alors même que l’image n’était plus modifiée.\nAussi, j’ai délégué la construction de cette URL au template post\/page.html.twig :\n{% set 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'|format(site.cloudinary.account, page.title|url_encode|replace({'%2C':'%252C'}), 'jamstatic\/twitter-card.png') %}\n{% set image = asset(cloudinary_url, {filename: 'cards\/%s.png'|format(page.id)}) %}\nEn pratique :\n\nCréation d’une image « modèle » (en veillant à garder de la place pour y intégrer le titre du billet)\nUtilisation de la fonction asset() pour manipuler l’image générée par Cloudinary :\nFormat automatique et qualité automatique\nDéfinition de la largeur maximum du texte avant retour à la ligne (1100 px) et positionnement du texte aux coordonnées 80:120\nUtilisation de la police de caractère Poppins 80 ultra bold interligne -30\nRemplacement des virgules présentes dans le titre par des espaces\nEncodage de l’URL obtenue\nEnfin, enregistrement de l’asset avec un nom de fichier \"slugifié\" afin d'être compatible avec les contraintes de nommage Windows, au format PNG\n\n\n\nExemple :\n\n\n\n\n\n\nExemple d’une Twitter Card\n\nConclusion\nPour 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é, Markdown, indépendant de la solution technique.",
      "content_html": "<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>\n<p>C’est la version sur laquelle vous lisez cette article 😊</p>\n<p>Pour rappel, avant le site ressemblait à ça :</p>\n<figure>\n<picture title=\"Capture d’écran de la v1 de Jamstatic.fr\">\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n</figure>\n<h2 id=\"pourquoi-changer-de-solution\">Pourquoi changer de solution ?</h2>\n<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>\nPuis, 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>\nBref, une perte de temps importante et une motivation se réduisant de jour en jour.</p>\n<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>\n<ol>\n<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>\n<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>\n</ol>\n<h2 id=\"comment\">Comment ?</h2>\n<p>Après le \"pourquoi ?\" intéressons nous maintenant à la partie la plus intéressante de ce billet, à savoir le \"comment ?\" 😊</p>\n<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>\n<ol>\n<li>J’effectue une modification ;</li>\n<li>Je lance un nouveau build ;</li>\n<li>J’effectue les ajustements nécessaires (en m’appuyant sur les messages d’erreur retournés) ;</li>\n<li>Je recommence jusqu’à ce que le build soit valide.</li>\n</ol>\n<h3 id=\"gestion-de-contenu\">Gestion de contenu</h3>\n<p>Nous pouvons séparer les contenus en 2 catégories :</p>\n<ol>\n<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>\n<li>Les <em>assets</em>, c’est à dire les illustrations et autres vidéos au sein des articles</li>\n</ol>\n<p>Aussi, je me suis tout d’abord concentré sur la réorganisation de ces contenus :</p>\n<ol>\n<li>Déplacement des fichiers <em>*.md</em> du dossier <code>content/</code> de Hugo vers le dossier <code>pages/</code> de Cecil</li>\n<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>\n<li>Déplacement des fichiers médias dans le dossier dédié de Cecil (<code>asset/</code>)</li>\n</ol>\n<h3 id=\"mise-en-forme-et-templates\">Mise en forme et templates</h3>\n<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>\n<ul>\n<li>La rationalisation et la factorisation des templates de manière à ne pas/plus dupliquer du code</li>\n<li>La suppression des templates inutiles, c’est à dire disponibles en natif avec Cecil (tel que celui du flux RSS)</li>\n</ul>\n<h3 id=\"dependances-et-scripts\">Dépendances et scripts</h3>\n<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>\n<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>\n<h3 id=\"image-de-partage\">Image de partage</h3>\n<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>\n<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>\n<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>\n<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>\n<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\">\n</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>\n<p>En pratique :</p>\n<ul>\n<li>Création d’une image « modèle » (en veillant à garder de la place pour y intégrer le titre du billet)</li>\n<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>\n<li>Format automatique et qualité automatique</li>\n<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>\n<li>Utilisation de la police de caractère Poppins 80 ultra bold interligne -30</li>\n<li>Remplacement des virgules présentes dans le titre par des espaces</li>\n<li>Encodage de l’URL obtenue</li>\n<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>\n</ol>\n</li>\n</ul>\n<p>Exemple :</p>\n<figure>\n<picture title=\"Exemple d’une Twitter Card\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Exemple d’une Twitter Card</figcaption>\n</figure>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>",
      "authors": [
        {
          "name": "arnaud"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://arnaudligny.fr/blog/moteur-de-recherche-algolia-site-statique/",
      "url": "https://arnaudligny.fr/blog/moteur-de-recherche-algolia-site-statique/",
      "title": "Un moteur de recherche sur un site statique grâce à Algolia",
      "summary": "Comment j'ai implémenté une fonctionnalité de recherche à la documentation de Cecil.app.",
      "date_published": "2021-07-26T00:00:00+00:00","content_text": "Billet initialement publié sur le blog d’Arnaud Ligny.\nQuand je travaillais à enrichir la documentation de Cecil, je me suis dit qu’il serait pertinent d’offrir un moteur de recherche full text aux utilisateurs.\n\n\n\n\n\n\nExemple de résultat de recherche\n\n\n\nQuelle solution technique ?\nGoogle CSE\nAlgolia\n\n\nÉtapes clefs\nCréer un index\nTransmettre l’index\nParamétrer l’index\n\n\nMise en œuvre\nCréation de l’index\nTransmission de l’index\n\n\nConclusion\n\nLa 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.\nAussi, 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.\n\n\n\n\n\n\nExemple de page de documentation\n\nQuelle solution technique ?\nGoogle CSE\nDans un premier temps j’ai expérimenté le moteur de recherche personnalisé de Google (CSE) qui permet de présenter les résultats indexés par Google pour un site donné (comme avec le préfixe site:).\nSi 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.\nAlgolia\nAussi, après plusieurs comparatifs, j’ai finalement retenu la solution Algolia pour les raisons suivantes :\n\nPertinence des résultats\nTarifs abordables (dont un plan gratuit)\nDocumentation riche\nNombreuses bibliothèques de code open source\n\nÉtapes clefs\nJe 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 Autocomplete (cf. la capture d’écran en début de billet).\nCréer un index\nAlgolia s’appuie sur un index, 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.\nCet index est une structure JSON 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.\nTransmettre l’index\nAlgolia propose plusieurs méthodes afin de transmettre ou de mettre à jour l’index :\n\nà la main, via le dashboard, en uploadant le fichier JSON\nen ligne de command, via Algolia CLI\nprogrammatiquement, en PHP, en JavaScript, etc.\n\nParamétrer l’index\nLe 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 dashboard.\nJe dis relativement car il peut être nécessaire d’effectuer quelques tests avant de maitriser les règles de priorisation des résultats.\n\n\n\n\n\n\nDashboard Algolia\n\nMise en œuvre\nDans le cas de la documentation de Cecil, il faut donc :\n\ncréer un fichier d’index au format JSON\nle transmettre à application Algolia\nafficher un champs de recherche avec auto-complétion\n\nCréation de l’index\nAvec Cecil 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 😊\nAinsi, l’objectif est de :\n\ncollecter le contenu des pages de la documentation (fichiers au format Markdown dans pages\/documentation), converti en HTML\ndé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 template Twig sur mesure\ngénérer un fichier algolia.json grâce aux formats de sortie\n\nRésultat cible\nLe fichier d’index va ressembler à ça :\n[\n  {\n    \"objectID\": \"documentation\/quick-start#download-cecil\",\n    \"page\": \"Quick Start\",\n    \"title\": \"Download Cecil\",\n    \"description\": \"Download cecil.phar from your terminal:\",\n    \"content\": \"...\",\n    \"date\": \"2020-12-19T00:00:00+00:00\",\n    \"href\": \"documentation\/quick-start\/#download-cecil\"\n  },\n  ...\n]\nCréation du template\nComme 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.\nS’agissant de rechercher dans la documentation, j’aurais pu créer ce template dans le dossier « documentation » (layouts\/documentation\/list.algolia.twig).\nMais 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 : layouts\/_default\/list.algolia.twig.\nAinsi, au sein du template, il suffit de boucler sur les contenus de la section « documentation », à l’aide du tag for :\n{% for p in site.pages|filter(p =&gt; p.section == 'documentation')|sort_by_weight %}\n...\n{% endfor %}\nEnsuite, 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 :\n{% set sections = p.content|preg_split('\/&lt;h3[^&gt;]*&gt;\/') %}\n\nLe filtre Twig preg_split à é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.\n\nDe 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 » :\n{\n  \"objectID\": \"Un ID unique\",\n  \"page\": \"Le nom de la page de documentation\",\n  \"title\": \"Le titre de section\",\n  \"description\": \"Le premier paragraphe de la section (utilisé pour illustrer l’aperçu des résiltats)\",\n  \"content\": \"Le contenu de la section, dans laquelle la recherche est effectuée\",\n  \"date\": \"La date de la page, utilisée pour pondérer les résultats\",\n  \"href\": \"Le lien vers la page de la documentation, combinée à une ancre afin d’emmener l’internaute à la bonne section\",\n}\n\nVoir le template complet sur GitHub.\n\nAssocier ce template à un format de sortie\nEn 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 😅\nPour régler ce soucis il suffit de compléter la configuration de la manière suivante :\noutput:\n  formats:\n    - name: algolia\n      mediatype: 'application\/json'\n      filename: 'algolia'\n      extension: 'json'\n  pagetypeformats:\n    homepage: ['html', 'atom', 'algolia']\nMaintenant Cecil sait que :\n\nLes pages dont la variable format a pour valeur le nom du format « algolia » peuvent utiliser un template de la forme &lt;layout&gt;.algolia.twig\nEnregistrer le fichier généré sous filename.extension, soit « algolia.json »\nLa page de type homepage (listant toutes les pages du site) doit être générée dans le format « algolia » (en plus de « html » et « atom »)\n\nEt voilà, l’index est maintenant généré et disponible à la racine du site généré : https:\/\/cecil.app\/algolia.json.\nTransmission de l’index\nComme indiqué précédemment, Algolia offre plusieurs méthodes d’envoi de l’index.\nDans 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.\nJ’ai ensuite cherché à automatisé cette procédure, et j’ai donc opté pour le client d’API JavaScript.\nCecil.app étant hébergé par Netlify, j’ai utilisé un plugin pour me simplifier la mise en oeuvre : Algolia Index Refresh Build Plugin :\n[[context.production.plugins]]\n  package = \"netlify-plugin-refresh-algolia\"\n  [context.production.plugins.inputs]\n    appId = \"APP_ID\"\n    indexName = \"INDEX\"\n    filePath = \"_site\/algolia.json\"\nFormulaire de recherche\n\n\n\n\n\n\nExemple de résultat de recherche\n\nLa mise en œuvre est relativement simple :\n\nintégrer un champ de saisi (élément input)\nlui associer la bibliothèque Autocomplete.js\n\nChamp de saisie :\n&lt;input type=\"text\" id=\"search-input\" placeholder=\"{% trans %}Search the Docs [Alt+S]{% endtrans %}\" accesskey=\"s\" \/&gt;\nAutocomplete.js :\n\/\/ client Algolia\nvar client = algoliasearch('APP_ID', 'API_KEY');\n\/\/ index dans lequel rechercher\nvar index = client.initIndex('INDEX');\n\/\/ fonction de recherche\nfunction newHitsSource(index, params) {\n  return function doSearch(query, cb) {\n    index\n      .search(query, params)\n      .then(function(res) {\n        cb(res.hits, res);\n      })\n      .catch(function(err) {\n        console.error(err);\n        cb([]);\n      });\n  };\n}\n\/\/ association de la lib au champ \"search-input\"\nautocomplete('#search-input', { hint: false }, [\n  {\n    source: newHitsSource(index, {\n      \/\/ paramètres de mise en valeur des résultats suggérés\n      hitsPerPage: 4,\n      attributesToHighlight: ['description', 'page', 'title'],\n      highlightPreTag: '&lt;strong&gt;',\n      highlightPostTag: '&lt;\/strong&gt;',\n      attributesToSnippet: ['description:25'],\n      snippetEllipsisText: '…'\n    }),\n    \/\/ clef d'affichage principale (ici le titre de la section)\n    displayKey: 'title'\n  }\n]).on('autocomplete:selected', function(event, suggestion, dataset, context) {\n  \/\/ au clic sur une suggestion on envoie l'internaute vers la page#ancre correspondante\n  window.location.href = '{{ url() }}' + suggestion.href;\n});\n\nVoir le template complet sur GitHub.\n\nEt voilà ! 🎉\nNotes :\n\nIl s’agit ici de la version 0 de Autocomplete.js qui reste fonctionnelle mais commence à vieillir\nLa 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…\n\nConclusion\nJe 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.\nPour tester, ça se passe par ici : https:\/\/cecil.app\/documentation\/",
      "content_html": "<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>\n<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>\n<figure>\n<picture title=\"Exemple de résultat de recherche\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Exemple de résultat de recherche</figcaption>\n</figure>\n<!-- break -->\n<div id=\"toc\"><ul>\n<li><a href=\"#quelle-solution-technique\">Quelle solution technique ?</a><ul>\n<li><a href=\"#google-cse\">Google CSE</a></li>\n<li><a href=\"#algolia\">Algolia</a></li>\n</ul>\n</li>\n<li><a href=\"#etapes-clefs\">Étapes clefs</a><ul>\n<li><a href=\"#creer-un-index\">Créer un index</a></li>\n<li><a href=\"#transmettre-l-index\">Transmettre l’index</a></li>\n<li><a href=\"#parametrer-l-index\">Paramétrer l’index</a></li>\n</ul>\n</li>\n<li><a href=\"#mise-en-艙uvre\">Mise en œuvre</a><ul>\n<li><a href=\"#creation-de-l-index\">Création de l’index</a></li>\n<li><a href=\"#transmission-de-l-index\">Transmission de l’index</a></li>\n</ul>\n</li>\n<li><a href=\"#conclusion\">Conclusion</a></li>\n</ul></div>\n<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>\n<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>\n<figure>\n<picture title=\"Exemple de page de documentation\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Exemple de page de documentation</figcaption>\n</figure>\n<h2 id=\"quelle-solution-technique\">Quelle solution technique ?</h2>\n<h3 id=\"google-cse\">Google <abbr title=\"Custom Search Engine\">CSE</abbr></h3>\n<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>\nSi 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>\n<h3 id=\"algolia\">Algolia</h3>\n<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>\n<ul>\n<li>Pertinence des résultats</li>\n<li><a href=\"https://www.algolia.com/pricing/\" target=\"_blank\" rel=\"noopener noreferrer\">Tarifs abordables</a> (dont un plan gratuit)</li>\n<li><a href=\"https://www.algolia.com/doc/\" target=\"_blank\" rel=\"noopener noreferrer\">Documentation riche</a></li>\n<li>Nombreuses bibliothèques de code <a href=\"https://github.com/algolia\" target=\"_blank\" rel=\"noopener noreferrer\">open source</a></li>\n</ul>\n<h2 id=\"etapes-clefs\">Étapes clefs</h2>\n<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>\n<h3 id=\"creer-un-index\">Créer un index</h3>\n<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>\n<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>\n<h3 id=\"transmettre-l-index\">Transmettre l’index</h3>\n<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>\n<ul>\n<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>\n<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>\n<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>\n</ul>\n<h3 id=\"parametrer-l-index\">Paramétrer l’index</h3>\n<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>\n<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>\n<figure>\n<picture title=\"Dashboard Algolia\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Dashboard Algolia</figcaption>\n</figure>\n<h2 id=\"mise-en-艙uvre\">Mise en œuvre</h2>\n<p>Dans le cas de la documentation de <a href=\"/tags/cecil\">Cecil</a>, il faut donc :</p>\n<ol>\n<li>créer un fichier d’index au format JSON</li>\n<li>le transmettre à application Algolia</li>\n<li>afficher un champs de recherche avec auto-complétion</li>\n</ol>\n<h3 id=\"creation-de-l-index\">Création de l’index</h3>\n<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>\n<p>Ainsi, l’objectif est de :</p>\n<ol>\n<li>collecter le contenu des pages de la documentation (fichiers au format Markdown dans <code>pages/documentation</code>), converti en HTML</li>\n<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>\n<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>\n</ol>\n<h4 id=\"resultat-cible\">Résultat cible</h4>\n<p>Le fichier d’index va ressembler à ça :</p>\n<pre><code class=\"language-json hljs json\">[\n  {\n    <span class=\"hljs-attr\">\"objectID\"</span>: <span class=\"hljs-string\">\"documentation/quick-start#download-cecil\"</span>,\n    <span class=\"hljs-attr\">\"page\"</span>: <span class=\"hljs-string\">\"Quick Start\"</span>,\n    <span class=\"hljs-attr\">\"title\"</span>: <span class=\"hljs-string\">\"Download Cecil\"</span>,\n    <span class=\"hljs-attr\">\"description\"</span>: <span class=\"hljs-string\">\"Download cecil.phar from your terminal:\"</span>,\n    <span class=\"hljs-attr\">\"content\"</span>: <span class=\"hljs-string\">\"...\"</span>,\n    <span class=\"hljs-attr\">\"date\"</span>: <span class=\"hljs-string\">\"2020-12-19T00:00:00+00:00\"</span>,\n    <span class=\"hljs-attr\">\"href\"</span>: <span class=\"hljs-string\">\"documentation/quick-start/#download-cecil\"</span>\n  },\n  ...\n]</code></pre>\n<h4 id=\"creation-du-template\">Création du template</h4>\n<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>\n<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>\nMais 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>\n<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>\n<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\">\n...\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<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>\n<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>\n<blockquote>\n<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>\n</blockquote>\n<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>\n<pre><code class=\"language-json hljs json\">{\n  <span class=\"hljs-attr\">\"objectID\"</span>: <span class=\"hljs-string\">\"Un ID unique\"</span>,\n  <span class=\"hljs-attr\">\"page\"</span>: <span class=\"hljs-string\">\"Le nom de la page de documentation\"</span>,\n  <span class=\"hljs-attr\">\"title\"</span>: <span class=\"hljs-string\">\"Le titre de section\"</span>,\n  <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>,\n  <span class=\"hljs-attr\">\"content\"</span>: <span class=\"hljs-string\">\"Le contenu de la section, dans laquelle la recherche est effectuée\"</span>,\n  <span class=\"hljs-attr\">\"date\"</span>: <span class=\"hljs-string\">\"La date de la page, utilisée pour pondérer les résultats\"</span>,\n  <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>,\n}</code></pre>\n<blockquote>\n<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>\n</blockquote>\n<h4 id=\"associer-ce-template-a-un-format-de-sortie\">Associer ce template à un format de sortie</h4>\n<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>\n<p>Pour régler ce soucis il suffit de compléter la configuration de la manière suivante :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">output:</span>\n  <span class=\"hljs-attr\">formats:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">algolia</span>\n      <span class=\"hljs-attr\">mediatype:</span> <span class=\"hljs-string\">'application/json'</span>\n      <span class=\"hljs-attr\">filename:</span> <span class=\"hljs-string\">'algolia'</span>\n      <span class=\"hljs-attr\">extension:</span> <span class=\"hljs-string\">'json'</span>\n  <span class=\"hljs-attr\">pagetypeformats:</span>\n    <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>\n<p>Maintenant Cecil sait que :</p>\n<ol>\n<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>\n<li>Enregistrer le fichier généré sous <code>filename.extension</code>, soit « algolia.json »</li>\n<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>\n</ol>\n<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>\n<h3 id=\"transmission-de-l-index\">Transmission de l’index</h3>\n<p>Comme <a href=\"#transmettre-l-index\">indiqué précédemment</a>, Algolia offre plusieurs méthodes d’envoi de l’index.</p>\n<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>\n<p>J’ai ensuite cherché à automatisé cette procédure, et j’ai donc opté pour le client d’API JavaScript.<br>\n<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>\n<pre><code class=\"language-toml hljs ini\"><span class=\"hljs-section\">[[context.production.plugins]]</span>\n  package = \"netlify-plugin-refresh-algolia\"\n  <span class=\"hljs-section\">[context.production.plugins.inputs]</span>\n    appId = \"APP_ID\"\n    indexName = \"INDEX\"\n    filePath = \"_site/algolia.json\"</code></pre>\n<h4 id=\"formulaire-de-recherche\">Formulaire de recherche</h4>\n<figure>\n<picture title=\"Exemple de résultat de recherche\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Exemple de résultat de recherche</figcaption>\n</figure>\n<p>La mise en œuvre est relativement simple :</p>\n<ol>\n<li>intégrer un champ de saisi (élément <code>input</code>)</li>\n<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>\n</ol>\n<p><strong>Champ de saisie :</strong></p>\n<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>\n<p><strong><em>Autocomplete.js</em> :</strong></p>\n<pre><code class=\"language-javascript hljs javascript\"><span class=\"hljs-comment\">// client Algolia</span>\n<span class=\"hljs-keyword\">var</span> client = algoliasearch(<span class=\"hljs-string\">'APP_ID'</span>, <span class=\"hljs-string\">'API_KEY'</span>);\n<span class=\"hljs-comment\">// index dans lequel rechercher</span>\n<span class=\"hljs-keyword\">var</span> index = client.initIndex(<span class=\"hljs-string\">'INDEX'</span>);\n<span class=\"hljs-comment\">// fonction de recherche</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">newHitsSource</span>(<span class=\"hljs-params\">index, params</span>) </span>{\n  <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>{\n    index\n      .search(query, params)\n      .then(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span>(<span class=\"hljs-params\">res</span>) </span>{\n        cb(res.hits, res);\n      })\n      .catch(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span>(<span class=\"hljs-params\">err</span>) </span>{\n        <span class=\"hljs-built_in\">console</span>.error(err);\n        cb([]);\n      });\n  };\n}\n<span class=\"hljs-comment\">// association de la lib au champ \"search-input\"</span>\nautocomplete(<span class=\"hljs-string\">'#search-input'</span>, { <span class=\"hljs-attr\">hint</span>: <span class=\"hljs-literal\">false</span> }, [\n  {\n    <span class=\"hljs-attr\">source</span>: newHitsSource(index, {\n      <span class=\"hljs-comment\">// paramètres de mise en valeur des résultats suggérés</span>\n      <span class=\"hljs-attr\">hitsPerPage</span>: <span class=\"hljs-number\">4</span>,\n      <span class=\"hljs-attr\">attributesToHighlight</span>: [<span class=\"hljs-string\">'description'</span>, <span class=\"hljs-string\">'page'</span>, <span class=\"hljs-string\">'title'</span>],\n      <span class=\"hljs-attr\">highlightPreTag</span>: <span class=\"hljs-string\">'&lt;strong&gt;'</span>,\n      <span class=\"hljs-attr\">highlightPostTag</span>: <span class=\"hljs-string\">'&lt;/strong&gt;'</span>,\n      <span class=\"hljs-attr\">attributesToSnippet</span>: [<span class=\"hljs-string\">'description:25'</span>],\n      <span class=\"hljs-attr\">snippetEllipsisText</span>: <span class=\"hljs-string\">'…'</span>\n    }),\n    <span class=\"hljs-comment\">// clef d'affichage principale (ici le titre de la section)</span>\n    <span class=\"hljs-attr\">displayKey</span>: <span class=\"hljs-string\">'title'</span>\n  }\n]).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>{\n  <span class=\"hljs-comment\">// au clic sur une suggestion on envoie l'internaute vers la page#ancre correspondante</span>\n  <span class=\"hljs-built_in\">window</span>.location.href = <span class=\"hljs-string\">'{{ url() }}'</span> + suggestion.href;\n});</code></pre>\n<blockquote>\n<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>\n</blockquote>\n<p>Et voilà ! 🎉</p>\n<p><strong>Notes :</strong></p>\n<ol>\n<li>Il s’agit ici de la version 0 de <em>Autocomplete.js</em> qui reste fonctionnelle mais commence à vieillir</li>\n<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>\n</ol>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>\n<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>",
      "authors": [
        {
          "name": "arnaud"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://arnaudligny.fr/blog/generer-et-heberger-un-site-statique-avec-github/",
      "url": "https://arnaudligny.fr/blog/generer-et-heberger-un-site-statique-avec-github/",
      "title": "Générer et héberger un site web statique avec GitHub",
      "summary": "Dans cet article j’explique comment mettre en pratique GitHub Pages et GitHub Actions, en utilisant Cecil comme générateur de site statique.",
      "date_published": "2021-06-29T00:00:00+00:00","content_text": "Billet initialement publié sur le blog d’Arnaud Ligny.\nGitHub fourni l’outillage nécessaire pour générer un site statique et pour l’héberger – gratuitement – grâce à GitHub Pages et GitHub Actions.\nDans cet article j’explique comment mettre en pratique GitHub Pages et GitHub Actions, en utilisant Cecil comme générateur de site statique.\n\n\nQu’est-ce que GitHub Pages ?\nQu’est-ce que GitHub Actions ?\nEn pratique, comment faire ?\nCréation du workflow\nDéfinition des  tâches et des étapes\nParamétrage de GitHub Pages\n\n\n\nQu’est-ce que GitHub Pages ?\n\n\n\n\n\nGitHub Pages est une solution d’hébergement de pages web statiques permettant de créer rapidement un site web associé à un projet GitHub.\\\nHistoriquement, cette branche dédiée d’un dépôt, utilise Jekyll par défaut pour générer à la volée des pages web à partir de son contenu (fichiers Markdown et HTML).\nAujourd’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.\nGitHub 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 :\n\nChoix de la branche (et du dossier : racine ou \/docs) à utiliser\nPossibilité de choisir un domaine personnalisé (via un enregistrement CNAME)\nActivation de HTTPS\n\nComme je viens de l’indiquer, l’idée ici de s’appuyer sur le SSG 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  ! 😀\nQu’est-ce que GitHub Actions ?\n\n\n\n\n\nGitHub Actions est la solution de GitHub pour automatiser des workflows de type intégration ou déploiement continue.\nLe principe est très proche de ce que propose des outils comme Jenkins, Travis CI ou encore GitLab CI.\nEn 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).\nCe 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) Actions mis à disposition par la communauté sur la marketplace.\nEn pratique, comment faire ?\nLe principe semble simple mais, en pratique, est-ce que c’est aussi facile à mettre en œuvre ?\\\nLa réponse est oui bien sûr ! 😀\nComme je l’indiquais en introduction, je vais illustrer mon propos en expliquant comment automatiser la génération d’un site statique avec Cecil et comment le déployer.\nCréation du workflow\nLes 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 :\n.github\/workflows\/\nDe là il suffit de créer un nouveau fichier avec l’extension yml ou yaml (par exemple build-and-deploy.yml) qui sera automatiquement reconnu comme un nouveau workflow accessible depuis l’onglet « Actions » du dépôt.\nDéfinition des  tâches et des étapes\nAvant de définir les tâches il est nécessaire de déterminer l’évènement déclencheur :\non:\n  push:\n    branches:\n    - master\nIci on considère que la branche de production est master et que la génération doit être déclenchée à chaque modification de code (push).\nEn pratique nous devons définir 2 tâches (jobs) :\n1. build : génération du site statique\nC’est là que la marketplace montrent tout son intérêt. En effet, pour chacune des étapes (steps) ci-dessous, j’utilise une action clef en main :\n\nObtention du code source via checkout\nGénération du site via Cecil-Action\n« Mise de côté » des fichiers générés via upload-artifact\n\n\nNote : la directive with permet de passer des options à l’action.\n\nbuild:\n  name: Build\n  # Utilisation d'une image Linux Ubuntu\n  runs-on: ubuntu-latest\n  steps:\n\n  - name: Checkout source\n    uses: actions\/checkout@v2\n    with:\n      # Inutile de récupérer tout l'historique : la dernière version suffit\n      fetch-depth: 1\n\n  - name: Build site with Cecil\n    uses: Cecilapp\/Cecil-Action@v3\n    with:\n      # Pour éviter les conflits, utilisation d'un nom spécifique\n      config: 'cecil.yml'\n\n  - name: Upload site to Artifacts\n    uses: actions\/upload-artifact@v2\n    with:\n      name: _site\n      path: _site\n      # La tâche ne sera pas exécutée si aucun fichier n'est généré\n      if-no-files-found: error\n2. deploy : déploiement des fichiers générés\n\nRécupération des fichiers générés via download-artifact\nDéploiement du site via GitHub-Pages-deploy\n\ndeploy:\n  name: Deploy\n  # La tâche 'deploy' est exécutée après la tâche 'build'\n  needs: build\n  runs-on: ubuntu-latest\n  steps:\n\n  - name: Download site from Artifacts\n    uses: actions\/download-artifact@v2\n    with:\n      name: _site\n      path: _site\n\n  - name: Deploy site to GitHub Pages\n    uses: Cecilapp\/GitHub-Pages-deploy@v3\n    env:\n      # Accès en écriture à la branche cible (`gh-pages`)\n      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    with:\n      # Adresse e-mail valide sur GitHub\n      email: arnaud@ligny.org\n\nNote : par défaut GitHub-Pages-deploy utilise le dossier _site comme source et le déploie dans la branche gh-pages.\n\nParamétrage de GitHub Pages\nEnfin, il reste à activer GitHub Pages au sein du dépôt via Settings &gt; Pages.\n\n\n\n\n\n\nSélectionner la branche cible gh-pages (ainsi que le dossier \/)\nPuis le domaine (si vous souhaitez le personnaliser)\nEt enfin activer HTTPS (si le domaine de référence est personnalisé)\n\n\n\n\n\n\nEt voilà comment générer et déployer automatiquement un site web statique, hébergé gratuitement ! 🎉\nRemarques :\n\nDans cet article j’ai utilisé Cecil Action mais j’aurais également pu effectuer la même démonstration avec une action Hugo ou Eleventy ;\nSi vous souhaitez tester par vous même, en moins d’une minute, je vous invite à essayer avec le template Single-GitHub-Page.\n",
      "content_html": "<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>\n<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>\n<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>\n<!-- break -->\n<div id=\"toc\"><ul>\n<li><a href=\"#qu-est-ce-que-github-pages\">Qu’est-ce que GitHub Pages ?</a></li>\n<li><a href=\"#qu-est-ce-que-github-actions\">Qu’est-ce que GitHub Actions ?</a></li>\n<li><a href=\"#en-pratique-comment-faire\">En pratique, comment faire ?</a><ul>\n<li><a href=\"#creation-du-workflow\">Création du workflow</a></li>\n<li><a href=\"#definition-des-taches-et-des-etapes\">Définition des  tâches et des étapes</a></li>\n<li><a href=\"#parametrage-de-github-pages\">Paramétrage de GitHub Pages</a></li>\n</ul>\n</li>\n</ul></div>\n<h2 id=\"qu-est-ce-que-github-pages\">Qu’est-ce que GitHub Pages ?</h2>\n<picture>\n<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\">\n<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\">\n<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;\">\n</picture>\n<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.\\\nHistoriquement, 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>\n<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>\n<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>\n<ol>\n<li>Choix de la branche (et du dossier : racine ou <code>/docs</code>) à utiliser</li>\n<li>Possibilité de choisir un domaine personnalisé (via un enregistrement <code>CNAME</code>)</li>\n<li>Activation de HTTPS</li>\n</ol>\n<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>\n<h2 id=\"qu-est-ce-que-github-actions\">Qu’est-ce que GitHub Actions ?</h2>\n<picture>\n<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\">\n<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\">\n<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;\">\n</picture>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"en-pratique-comment-faire\">En pratique, comment faire ?</h2>\n<p>Le principe semble simple mais, en pratique, est-ce que c’est aussi facile à mettre en œuvre ?\\\nLa réponse est oui bien sûr ! 😀</p>\n<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>\n<h3 id=\"creation-du-workflow\">Création du workflow</h3>\n<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>\n<pre><code class=\"language-plaintext hljs plaintext\">.github/workflows/</code></pre>\n<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>\n<h3 id=\"definition-des-taches-et-des-etapes\">Définition des  tâches et des étapes</h3>\n<p>Avant de définir les tâches il est nécessaire de déterminer l’évènement déclencheur :</p>\n<pre><code class=\"language-yml hljs yaml\"><span class=\"hljs-attr\">on:</span>\n  <span class=\"hljs-attr\">push:</span>\n    <span class=\"hljs-attr\">branches:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">master</span></code></pre>\n<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>\n<p>En pratique nous devons définir <strong>2 tâches</strong> (<code>jobs</code>) :</p>\n<h4 id=\"1-build-generation-du-site-statique\">1. <code>build</code> : génération du site statique</h4>\n<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>\n<ol>\n<li>Obtention du code source via <a href=\"https://github.com/marketplace/actions/checkout\" target=\"_blank\" rel=\"noopener noreferrer\"><code>checkout</code></a></li>\n<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>\n<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>\n</ol>\n<blockquote>\n<p>Note : la directive <code>with</code> permet de passer des options à l’action.</p>\n</blockquote>\n<pre><code class=\"language-yml hljs yaml\"><span class=\"hljs-attr\">build:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Build</span>\n  <span class=\"hljs-comment\"># Utilisation d'une image Linux Ubuntu</span>\n  <span class=\"hljs-attr\">runs-on:</span> <span class=\"hljs-string\">ubuntu-latest</span>\n  <span class=\"hljs-attr\">steps:</span>\n\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Checkout</span> <span class=\"hljs-string\">source</span>\n    <span class=\"hljs-attr\">uses:</span> <span class=\"hljs-string\">actions/checkout@v2</span>\n    <span class=\"hljs-attr\">with:</span>\n      <span class=\"hljs-comment\"># Inutile de récupérer tout l'historique : la dernière version suffit</span>\n      <span class=\"hljs-attr\">fetch-depth:</span> <span class=\"hljs-number\">1</span>\n\n  <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>\n    <span class=\"hljs-attr\">uses:</span> <span class=\"hljs-string\">Cecilapp/Cecil-Action@v3</span>\n    <span class=\"hljs-attr\">with:</span>\n      <span class=\"hljs-comment\"># Pour éviter les conflits, utilisation d'un nom spécifique</span>\n      <span class=\"hljs-attr\">config:</span> <span class=\"hljs-string\">'cecil.yml'</span>\n\n  <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>\n    <span class=\"hljs-attr\">uses:</span> <span class=\"hljs-string\">actions/upload-artifact@v2</span>\n    <span class=\"hljs-attr\">with:</span>\n      <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">_site</span>\n      <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">_site</span>\n      <span class=\"hljs-comment\"># La tâche ne sera pas exécutée si aucun fichier n'est généré</span>\n      <span class=\"hljs-attr\">if-no-files-found:</span> <span class=\"hljs-string\">error</span></code></pre>\n<h4 id=\"2-deploy-deploiement-des-fichiers-generes\">2. <code>deploy</code> : déploiement des fichiers générés</h4>\n<ol>\n<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>\n<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>\n</ol>\n<pre><code class=\"language-yml hljs yaml\"><span class=\"hljs-attr\">deploy:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Deploy</span>\n  <span class=\"hljs-comment\"># La tâche 'deploy' est exécutée après la tâche 'build'</span>\n  <span class=\"hljs-attr\">needs:</span> <span class=\"hljs-string\">build</span>\n  <span class=\"hljs-attr\">runs-on:</span> <span class=\"hljs-string\">ubuntu-latest</span>\n  <span class=\"hljs-attr\">steps:</span>\n\n  <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>\n    <span class=\"hljs-attr\">uses:</span> <span class=\"hljs-string\">actions/download-artifact@v2</span>\n    <span class=\"hljs-attr\">with:</span>\n      <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">_site</span>\n      <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">_site</span>\n\n  <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>\n    <span class=\"hljs-attr\">uses:</span> <span class=\"hljs-string\">Cecilapp/GitHub-Pages-deploy@v3</span>\n    <span class=\"hljs-attr\">env:</span>\n      <span class=\"hljs-comment\"># Accès en écriture à la branche cible (`gh-pages`)</span>\n      <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>\n    <span class=\"hljs-attr\">with:</span>\n      <span class=\"hljs-comment\"># Adresse e-mail valide sur GitHub</span>\n      <span class=\"hljs-attr\">email:</span> <span class=\"hljs-string\">arnaud@ligny.org</span></code></pre>\n<blockquote>\n<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>\n</blockquote>\n<h3 id=\"parametrage-de-github-pages\">Paramétrage de GitHub Pages</h3>\n<p>Enfin, il reste à activer <em>GitHub Pages</em> au sein du dépôt via <code>Settings</code> &gt; <code>Pages</code>.</p>\n<picture>\n<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\">\n<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\">\n<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;\">\n</picture>\n<ol>\n<li>Sélectionner la branche cible <code>gh-pages</code> (ainsi que le dossier <code>/</code>)</li>\n<li>Puis le domaine (si vous souhaitez le personnaliser)</li>\n<li>Et enfin activer HTTPS (si le domaine de référence est personnalisé)</li>\n</ol>\n<picture>\n<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\">\n<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\">\n<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;\">\n</picture>\n<p>Et voilà comment générer et déployer automatiquement un site web statique, hébergé gratuitement ! 🎉</p>\n<p>Remarques :</p>\n<ul>\n<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>\n<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>\n</ul>",
      "authors": [
        {
          "name": "arnaud"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://arnaudligny.fr/blog/un-site-e-commerce-avec-cecil-et-snipcart/",
      "url": "https://arnaudligny.fr/blog/un-site-e-commerce-avec-cecil-et-snipcart/",
      "title": "Créer un site e-commerce avec Cecil et Snipcart",
      "summary": "Comment créer un site e-commerce statique performant et peu couteux grâce à Cecil et Snipcart.",
      "date_published": "2021-06-24T00:00:00+00:00","content_text": "Billet initialement publié sur le blog d’Arnaud Ligny.\nEn début d’année ma chérie terminait la campagne Ulule de son projet Paysages à vélo et se posait la question de continuer la vente de ses créations via une boutique en ligne.\nElle m’a alors sollicité pour l’aider à concevoir et construire cette boutique. Elle hésitait entre une solution clef en main telle que Shopify ou une solution basée sur un framework e-commerce tel WooCommerce.\nNé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.\nJe lui ai alors proposé de créer un site web statique avec Cecil auquel nous brancherions la solution e-commerce Snipcart afin de dynamiser les interactions utilisateur.\n\n\nPourquoi un site statique ?\nPourquoi Snipcart ?\nMise en œuvre\nCréation du catalogue\nTemplates et intégration Snipcart\nPersonnalisation du tunnel d’achat\n\n\nGestion de contenu (CMS)\nConclusion\n\nPourquoi un site statique ?\nJe suis un fervent promoteur de l’approche statique pour la diffusion de sites web de contenu pour les raisons suivantes (entre autres) :\n\nPerformance : une fois généré, le site n’a pas plus besoin d’être interprété par le serveur, juste d’être servi ;\nSimplicité : pas de base de données à maintenir car les données sont stockées dans des fichiers plats (Markdown + YAML) ;\nPortabilité : peut être hébergé sur n’importe serveur web et peut donc être migré facilement selon les besoins.\n\nDans le cas de ce projet j’ai donc utilisé mon propre générateur de site statique : Cecil.\nPourquoi Snipcart ?\nSnipcart n’est pas une solution e-commerce clef en main mais plutôt un « checkout » (tunnel d’achat) à ajouter à n’importe quel site web.\nIl 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.\nLe 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.\nIntérêt de cette approche et de Snipcart en particulier :\n\nIndépendance concernant la gestion du catalogue ;\nPeu ou pas de développement spécifique, principalement de la personnalisation ;\nTarif honnête (2% des ventes) ;\nSécurisation des transactions portée par la solution de paiement (ici Stripe).\n\nMise en œuvre\nCréation du catalogue\nLe catalogue de Paysages à vélo est très simple : il s’agit de proposer moins d’une dizaine d’affiches dans 2 formats d’impression (A3 et A5).\nEn pratique nous avons 6 modèles composés d’1 variant « format ».\nLes attributs et le texte de la fiche produit sont définis dans un fichier Markdown (avec un « front matter ») :\npages\/products\n|_ index.md\n|_ 1.pink-gravel.md\n|_ 2.purple-cargo.md\n|_ 3.yellow-longtail.md\n|_ 4.blue-folding-bike.md\n|_ 5.ultra-violet-gang.md\n|_ 6.lemon-lovers.md\nLes produits partagent les mêmes caractéristiques de base, qui peuvent donc être mutualisées à la racine de la section products via le fichier pages\/products\/index.md.\nLes attributs sont définis via des variables au format YAML :\n---\ncascade:\n  price: 23\n  variants:\n  - name: Format\n    options:\n    - value: A3\n      html: \"Affiche A3 - 23 €\"\n      price: 0\n    - value: A5\n      html: \"Carte A5 - 8 €\"\n      price: -15\n---\nJ’ai utilisé la variable spéciale cascade qui permet de faire hériter à toutes pages de la section les variables qu’elle contient :\n\nprice : le prix de référence\nvariants : qui caractérise les déclinaisons pour chacun des produits, en l’occurrence le format d’impression\noptions :\nvalue : la valeur du format (ex : « A3 »)\nhtml : le texte affiché dans la liste déroulante\nprice : le prix modifié par rapport au prix de référence (qui peut être négatif)\n\n\n\n\n\nEnsuite chacun des produits est caractérisé via son propre fichier Markdown, par exemple pages\/products\/1.pink-gravel.md :\n---\ntitle: \"Pink gravel\"\ndescription: \"Femme ridant en toute liberté au pieds des montagnes.\"\nname: \"#01 Pink gravel\"\nimage: \"\/images\/products\/01-Pink-gravel-A3_S.png\"\ngallery:\n- \"\/images\/products\/01-Pink-gravel-A5_S.png\"\n- \"\/images\/products\/01-Pink-gravel-ZOOM_S.png\"\npublished: true\n---\n**Gavarnie, Hautes-Pyrénées.**  \n**Femme ridant en toute liberté au pieds des montagnes.**\n\n_Impression numérique sans bordure sur papier couché premium semi mat 200 g (carte A5 300 g). \nLes affiches sont toutes signées à la main._\nTemplates et intégration Snipcart\nListe des produits\nLa 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.\nAinsi le template Twig layouts\/index.html.twig liste l’ensemble des « pages » contenus dans la section products triées par « poids » inverse (donc la dernière création en première position) :\n{% extends 'page.html.twig' %}\n\n{% block content %}\n&lt;div class=\"hero\"&gt;\n  {{~ page.content ~}}\n&lt;\/div&gt;\n&lt;div class=\"products\"&gt;\n  {%- for product in site.pages.all|filter_by('section', 'products')|sort_by_weight|reverse ~%}\n    {%~ include 'components\/product.html.twig' with {'product': product, 'home': true} only ~%}\n  {%- endfor ~%}\n&lt;\/div&gt;\n{% endblock %}\nFiche produit\nLa fiche produit (un composant Twig réutilisable) va afficher :\n\nLes informations : nom, description, photos, etc.\nLe choix du format (options du variant format), la saisie de la quantité souhaitée et le bouton « Ajouter au panier »\n\nConcentrons nous sur le cœur de la fiche produit, à savoir l’ajout au panier :\n\n\n\n\n\n\nFormulaire d’ajout au panier\n\n&lt;div class=\"product__details\"&gt;\n  {%- if product.variants is defined ~%}\n  &lt;div&gt;\n    {%- for variant in product.variants ~%}\n      {%- if variant.options is defined ~%}\n    &lt;label for=\"{{ productId }}-{{ variant.name|lower }}\"&gt;{{ variant.name }}&lt;\/label&gt;\n    &lt;select id=\"{{ productId }}-{{ variant.name|lower }}\" class=\"{{ variant.name|lower }}\"&gt;\n        {%- for option in variant.options ~%}\n      &lt;option value=\"{{ option.value }}\"&gt;{{ option.html }}&lt;\/option&gt;\n        {%- endfor ~%}\n    &lt;\/select&gt;\n      {%- endif ~%}\n    {%- endfor ~%}\n  &lt;\/div&gt;\n  {%- endif ~%}\n  &lt;div&gt;\n    &lt;label for=\"{{ productId }}-qty\"&gt;Quantité&lt;\/label&gt;\n    &lt;input type=\"number\" id=\"{{ productId }}-qty\" class=\"qty\" min=\"1\" max=\"10\" value=\"1\" \/&gt;\n  &lt;\/div&gt;\n  {%~ include 'components\/add-item.html.twig' with {'productId':productId,'product':product} only ~%}\n&lt;\/div&gt;\nCette portion du template layouts\/components\/product.html.twig est composée de 3 parties :\n\nLa liste déroulantes des formats, en faisant une boucle sur l‘ensemble des variants disponibles (ici uniquement format), puis une autre boucle sur l’ensemble des options (A3 et A5) ;\nLe champ de saisi de la quantité ;\nLe bouton d’ajout au panier (représenté par le composant add-item.html.twig).\n\nC’est le composant bouton qui porte les attributs permettant l’ajout du produit au panier Snipcart.\nIntégration Snipcart\nL’intégration de Snipcart est simple, et nécessite :\n\nLa feuille de style de référence ;\nUn ou plusieurs boutons d’ajout au panier, portant les attributs du produit : identifiant, URL, nom, prix, etc. ;\nUne balise &lt;div&gt; invisible (permettant affichage du panier) portant la clef d’API Snipcart ;\nL’applicatif à proprement parlé via un fichier JavaScript.\n\n&lt;link rel=\"stylesheet\" href=\"https:\/\/cdn.snipcart.com\/themes\/v3.0.11\/default\/snipcart.css\" \/&gt;\n\n&lt;button class=\"snipcart-add-item\"\n  data-item-id=\"product-1\"\n  data-item-url=\"\/\"\n  data-item-name=\"Product #1\"\n  data-item-price=\"10.99\"\n&gt;Add to cart&lt;\/button&gt;\n\n&lt;div hidden id=\"snipcart\" data-api-key=\"MzMxN2Y0ODMtOWNhMy00YzUzLWFiNTYtZjMwZTRkZDcxYzM4\"&gt;&lt;\/div&gt;\n\n&lt;script src=\"https:\/\/cdn.snipcart.com\/themes\/v3.0.11\/default\/snipcart.js\"&gt;&lt;\/script&gt;\n\nDémo : https:\/\/codepen.io\/thatfrankdev\/pen\/xxwRXQw?editors=1000\nTemplate de Paysages à vélo : layouts\/components\/add-item.html.twig\n\nPersonnalisation du tunnel d’achat\nJ’ai également pris le temps de personnaliser le tunnel d’achat à la fois au niveau du rendu graphique et des étapes.\n\n\n\n\n\n\nExemple de panier\n\nPersonnalisation du rendu\nConcernant 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.\nPar exemple, pour remplacer la police de caractère :\n.snipcart {\n  font-family: 'Roboto Condensed', sans-serif;\n}\nLa feuille de style Sass de Paysages à vélo disponible sur GitHub.\n\nRemarque : depuis la version 3.2, Snipcart a introduit la notion de « Theming » qui facilite grandement la personnalisation via des propriétés CSS.\n\nPersonnalisation des textes\nLes textes de l’interface de Snipcart sont disponibles en français (à laquelle j’ai d’ailleurs apporté ma contribution) sans paramétrage particulier (autre qu’en définissant l’attribut lang de la balise &lt;html&gt;) mais si vous souhaitez personnaliser les textes, ça reste possible en chargeant son propre fichier de langue :\ndocument.addEventListener('snipcart.ready', function() {\n  fetch('\/snipcart\/{{ language }}.json')\n    .then(response =&gt; response.json())\n    .then(translation =&gt; Snipcart.api.session.setLanguage('{{ language }}', translation))\n});\nPersonnalisation des formulaires\nSnipcart permet également de modifier et d’enrichir les étapes du tunnel d’achat via des templates Vue.js :\n&lt;div hidden id=\"snipcart\" data-api-key=\"{{ site.snipcart.apikey }}\" data-templates-url=\"\/snipcart\/templates.tpl\"&gt;&lt;\/div&gt;\nAinsi, dans le cas de Paysages à vélo j’ai :\n\nModifié l’affichage des lignes du panier afin d’y indiquer le format d’impression sélectionné à côté du nom du produit ;\nDésactivé la suggestion d’adresse (qui n’est pas très fiable sur le territoire français) ;\nAjouté un champ de saisi d’un message cadeau.\n\nPar exemple, dans le cas du champ de saisi du message cadeau, le code ressemble à ça :\n&lt;shipping-address section=\"bottom\"&gt;\n  &lt;fieldset class=\"snipcart-form__set\"&gt;\n    &lt;hr class=\"snipcart-form__separator\" \/&gt;\n    &lt;!-- Gift message --&gt;\n    &lt;div class=\"snipcart-form__field\"&gt;\n      &lt;snipcart-label class=\"snipcart__font--tiny\" for=\"Message cadeau\"&gt;Message cadeau&lt;\/snipcart-label&gt;\n      &lt;snipcart-input name=\"Message cadeau\"&gt;&lt;\/snipcart-input&gt;\n      &lt;p class=\"snipcart__font--tiny snipcart-form__footer\"&gt;\n        (Votre message sera écrit à la main sur une carte, ajoutée au colis)\n      &lt;\/p&gt;\n    &lt;\/div&gt;\n  &lt;\/fieldset&gt;\n&lt;\/shipping-address&gt;\n\nSi vous voulez en voir plus le code source est disponible sur GitHub.\n\nGestion de contenu (CMS)\nLa 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.\nNéanmoins il peut s’avérer plus commode et plus agréable de pouvoir s’appuyer sur un CMS : dans le cas de Paysages à vélo, j’ai retenu Forestry pour sa simplicité de mise en œuvre et d’utilisation.\nDe plus Forestry offre un fonctionnalité de prévisualisation, en contexte, très efficace !\n\n\n\nDémo Forestry\n\nConclusion\nJ’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 personnaliser les frais de port via webhook).\nEt surtout : 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 ! 🛒😊\nEnfin, je vous invite à :\n\nÉtudier le code source du projet si vous souhaitez en savoir plus et vous inspirer ;\nJeter un œil à mon générateur de site statique : Cecil ;\nConsulter le site officiel de Snipcart.\n",
      "content_html": "<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>\n<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>\n<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>\nNé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>\n<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>\n<!-- break -->\n<div id=\"toc\"><ul>\n<li><a href=\"#pourquoi-un-site-statique\">Pourquoi un site statique ?</a></li>\n<li><a href=\"#pourquoi-snipcart\">Pourquoi Snipcart ?</a></li>\n<li><a href=\"#mise-en-艙uvre\">Mise en œuvre</a><ul>\n<li><a href=\"#creation-du-catalogue\">Création du catalogue</a></li>\n<li><a href=\"#templates-et-integration-snipcart\">Templates et intégration Snipcart</a></li>\n<li><a href=\"#personnalisation-du-tunnel-d-achat\">Personnalisation du tunnel d’achat</a></li>\n</ul>\n</li>\n<li><a href=\"#gestion-de-contenu-cms\">Gestion de contenu (CMS)</a></li>\n<li><a href=\"#conclusion\">Conclusion</a></li>\n</ul></div>\n<h2 id=\"pourquoi-un-site-statique\">Pourquoi un site statique ?</h2>\n<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>\n<ul>\n<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>\n<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>\n<li><strong>Portabilité</strong> : peut être hébergé sur n’importe serveur web et peut donc être migré facilement selon les besoins.</li>\n</ul>\n<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>\n<h2 id=\"pourquoi-snipcart\">Pourquoi Snipcart ?</h2>\n<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>\n<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>\n<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>\n<p>Intérêt de cette approche et de Snipcart en particulier :</p>\n<ul>\n<li><strong>Indépendance</strong> concernant la gestion du catalogue ;</li>\n<li><strong>Peu ou pas de développement</strong> spécifique, principalement de la personnalisation ;</li>\n<li><strong>Tarif</strong> honnête (2% des ventes) ;</li>\n<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>\n</ul>\n<h2 id=\"mise-en-艙uvre\">Mise en œuvre</h2>\n<h3 id=\"creation-du-catalogue\">Création du catalogue</h3>\n<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>\nEn pratique nous avons 6 modèles composés d’1 variant « format ».</p>\n<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>\n<pre><code>pages/products\n|_ index.md\n|_ 1.pink-gravel.md\n|_ 2.purple-cargo.md\n|_ 3.yellow-longtail.md\n|_ 4.blue-folding-bike.md\n|_ 5.ultra-violet-gang.md\n|_ 6.lemon-lovers.md</code></pre>\n<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>\nLes 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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">cascade:</span>\n  <span class=\"hljs-attr\">price:</span> <span class=\"hljs-number\">23</span>\n  <span class=\"hljs-attr\">variants:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Format</span>\n    <span class=\"hljs-attr\">options:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">value:</span> <span class=\"hljs-string\">A3</span>\n      <span class=\"hljs-attr\">html:</span> <span class=\"hljs-string\">\"Affiche A3 - 23 €\"</span>\n      <span class=\"hljs-attr\">price:</span> <span class=\"hljs-number\">0</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">value:</span> <span class=\"hljs-string\">A5</span>\n      <span class=\"hljs-attr\">html:</span> <span class=\"hljs-string\">\"Carte A5 - 8 €\"</span>\n      <span class=\"hljs-attr\">price:</span> <span class=\"hljs-number\">-15</span>\n<span class=\"hljs-meta\">---</span></code></pre>\n<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>\n<ul>\n<li><code>price</code> : le prix de référence</li>\n<li><code>variants</code> : qui caractérise les déclinaisons pour chacun des produits, en l’occurrence le <em>format</em> d’impression<ul>\n<li><code>options</code> :<ul>\n<li><code>value</code> : la valeur du format (ex : « A3 »)</li>\n<li><code>html</code> : le texte affiché dans la liste déroulante</li>\n<li><code>price</code> : le prix modifié par rapport au prix de référence (qui peut être négatif)</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p>Ensuite chacun des produits est caractérisé via son propre fichier Markdown, par exemple <code>pages/products/1.pink-gravel.md</code> :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">\"Pink gravel\"</span>\n<span class=\"hljs-attr\">description:</span> <span class=\"hljs-string\">\"Femme ridant en toute liberté au pieds des montagnes.\"</span>\n<span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">\"#01 Pink gravel\"</span>\n<span class=\"hljs-attr\">image:</span> <span class=\"hljs-string\">\"/images/products/01-Pink-gravel-A3_S.png\"</span>\n<span class=\"hljs-attr\">gallery:</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">\"/images/products/01-Pink-gravel-A5_S.png\"</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">\"/images/products/01-Pink-gravel-ZOOM_S.png\"</span>\n<span class=\"hljs-attr\">published:</span> <span class=\"hljs-literal\">true</span>\n<span class=\"hljs-meta\">---</span>\n<span class=\"hljs-string\">**Gavarnie,</span> <span class=\"hljs-string\">Hautes-Pyrénées.**</span>  \n<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>\n\n<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> \n<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>\n<h3 id=\"templates-et-integration-snipcart\">Templates et intégration Snipcart</h3>\n<h4 id=\"liste-des-produits\">Liste des produits</h4>\n<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>\n<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>\n<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\">\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">block</span></span> content %}</span><span class=\"xml\">\n<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>\n  </span><span class=\"hljs-template-variable\">{{~ page.content ~}}</span><span class=\"xml\">\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n<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>\n  </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\">\n    </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\">\n  </span><span class=\"hljs-template-tag\">{%- <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> ~%}</span><span class=\"xml\">\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endblock</span></span> %}</span></code></pre>\n<h4 id=\"fiche-produit\">Fiche produit</h4>\n<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>\n<ol>\n<li>Les informations : nom, description, photos, etc.</li>\n<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>\n</ol>\n<p>Concentrons nous sur le cœur de la fiche produit, à savoir l’ajout au panier :</p>\n<figure>\n<picture title=\"Formulaire d’ajout au panier\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Formulaire d’ajout au panier</figcaption>\n</figure>\n<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>\n  </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\">\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div</span>&gt;</span>\n    </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\">\n      </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\">\n    <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>\n    <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>\n        </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\">\n      <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>\n        </span><span class=\"hljs-template-tag\">{%- <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> ~%}</span><span class=\"xml\">\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">select</span>&gt;</span>\n      </span><span class=\"hljs-template-tag\">{%- <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> ~%}</span><span class=\"xml\">\n    </span><span class=\"hljs-template-tag\">{%- <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> ~%}</span><span class=\"xml\">\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  </span><span class=\"hljs-template-tag\">{%- <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> ~%}</span><span class=\"xml\">\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div</span>&gt;</span>\n    <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>\n    <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>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  </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\">\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span></span></code></pre>\n<p>Cette portion du template <code>layouts/components/product.html.twig</code> est composée de 3 parties :</p>\n<ol>\n<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>\n<li>Le champ de saisi de la quantité ;</li>\n<li>Le bouton d’ajout au panier (représenté par le composant <code>add-item.html.twig</code>).</li>\n</ol>\n<p>C’est le composant bouton qui porte les attributs permettant l’ajout du produit au panier Snipcart.</p>\n<h4 id=\"integration-snipcart\">Intégration Snipcart</h4>\n<p>L’intégration de Snipcart est simple, et nécessite :</p>\n<ol>\n<li>La feuille de style de référence ;</li>\n<li>Un ou plusieurs boutons d’ajout au panier, portant les attributs du produit : identifiant, URL, nom, prix, etc. ;</li>\n<li>Une balise <code>&lt;div&gt;</code> invisible (permettant affichage du panier) portant la clef d’API Snipcart ;</li>\n<li>L’applicatif à proprement parlé via un fichier JavaScript.</li>\n</ol>\n<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>\n\n<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>\n  <span class=\"hljs-attr\">data-item-id</span>=<span class=\"hljs-string\">\"product-1\"</span>\n  <span class=\"hljs-attr\">data-item-url</span>=<span class=\"hljs-string\">\"/\"</span>\n  <span class=\"hljs-attr\">data-item-name</span>=<span class=\"hljs-string\">\"Product #1\"</span>\n  <span class=\"hljs-attr\">data-item-price</span>=<span class=\"hljs-string\">\"10.99\"</span>\n&gt;</span>Add to cart<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">button</span>&gt;</span>\n\n<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>\n\n<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>\n<ul>\n<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>\n<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>\n</ul>\n<h3 id=\"personnalisation-du-tunnel-d-achat\">Personnalisation du tunnel d’achat</h3>\n<p>J’ai également pris le temps de personnaliser le tunnel d’achat à la fois au niveau du rendu graphique et des étapes.</p>\n<figure>\n<picture title=\"Exemple de panier\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Exemple de panier</figcaption>\n</figure>\n<h4 id=\"personnalisation-du-rendu\">Personnalisation du rendu</h4>\n<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>\n<p>Par exemple, pour remplacer la police de caractère :</p>\n<pre><code class=\"language-css hljs css\"><span class=\"hljs-selector-class\">.snipcart</span> {\n  <span class=\"hljs-attribute\">font-family</span>: <span class=\"hljs-string\">'Roboto Condensed'</span>, sans-serif;\n}</code></pre>\n<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>\n<blockquote>\n<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>\n</blockquote>\n<h4 id=\"personnalisation-des-textes\">Personnalisation des textes</h4>\n<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>\n<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>{\n  fetch(<span class=\"hljs-string\">'/snipcart/{{ language }}.json'</span>)\n    .then(<span class=\"hljs-function\"><span class=\"hljs-params\">response</span> =&gt;</span> response.json())\n    .then(<span class=\"hljs-function\"><span class=\"hljs-params\">translation</span> =&gt;</span> Snipcart.api.session.setLanguage(<span class=\"hljs-string\">'{{ language }}'</span>, translation))\n});</code></pre>\n<h4 id=\"personnalisation-des-formulaires\">Personnalisation des formulaires</h4>\n<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>\n<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>\n<p>Ainsi, dans le cas de <em>Paysages à vélo</em> j’ai :</p>\n<ol>\n<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>\n<li>Désactivé la suggestion d’adresse (qui n’est pas très fiable sur le territoire français) ;</li>\n<li>Ajouté un champ de saisi d’un message cadeau.</li>\n</ol>\n<p>Par exemple, dans le cas du champ de saisi du message cadeau, le code ressemble à ça :</p>\n<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>\n  <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>\n    <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>\n    <span class=\"hljs-comment\">&lt;!-- Gift message --&gt;</span>\n    <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>\n      <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>\n      <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>\n      <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>\n        (Votre message sera écrit à la main sur une carte, ajoutée au colis)\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">p</span>&gt;</span>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">fieldset</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">shipping-address</span>&gt;</span></code></pre>\n<blockquote>\n<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>\n</blockquote>\n<h2 id=\"gestion-de-contenu-cms\">Gestion de contenu (CMS)</h2>\n<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>\n<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>\n<p>De plus Forestry offre un fonctionnalité de prévisualisation, en contexte, très efficace !</p>\n<!--\n<video controls preload=\"none\" poster=\"/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/forestry-preview-demo.mp4_poster.webp\">\n  <source src=\"/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/forestry-preview-demo.webm\" type=\"video/webm\">\n  <source src=\"/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/forestry-preview-demo.mp4\" type=\"video/mp4\">\n</video>\n-->\n<p><figure>\n<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;\">\n<figcaption>Démo Forestry</figcaption>\n</figure></p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>\n<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>\n<p>Enfin, je vous invite à :</p>\n<ul>\n<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>\n<li>Jeter un œil à mon générateur de site statique : <a href=\"https://cecil.app/\" target=\"_blank\" rel=\"noopener noreferrer\">Cecil</a> ;</li>\n<li>Consulter le site officiel de <a href=\"https://snipcart.com/fr\" target=\"_blank\" rel=\"noopener noreferrer\">Snipcart</a>.</li>\n</ul>",
      "authors": [
        {
          "name": "arnaud"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2021/03/09/11000-pages-statiques/",
      "url": "https://jamstatic.fr/2021/03/09/11000-pages-statiques/",
      "title": "Un site statique de 11 000 pages, c'est possible ?",
      "summary": "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 ?",
      "date_published": "2021-03-09T14:00:00+00:00","content_text": "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 ?\nLe contexte\nTout 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.  \nAu 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.\nIl désire donc basculer une bonne partie des sites actuels sur des sites statiques.  \nMais 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.\nChoix des générateurs et du CMS Headless\nCôté CMS, le choix est déjà arrêté sur Craft CMS. 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.\nLe 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.\nComme le mode prévisualisation est un prérequis, cela élimine déjà tous les GSS (générateur de site statique) pur HTML (Hugo, Eleventy, etc.) sur lequel on ne bénéficie pas de cette fonctionnalité.\nOn 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.\nGastby 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.\nPour être 100% honnête, cet article a mis de côté Gastby au profit de Next.js dès le départ.\nNext.js\nNext.js 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).\nUniquement 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).\nNuxt.js\nNuxt.js 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).\nIl 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.\nRien de confirmé pour le moment, mais la version 3.0 de Nuxt.js devrait proposer une fonctionnalité équivalente.\nCommençons avec Next.js\nNous 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.\nNéanmoins ces deux fonctionnalités et le mode prévisualisation ne sont pas disponibles dans le mode entièrement statique (export HTML). Donc pour Next.js, le mode hybride est obligatoire 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.\nGénération des 11 000 pages et premier blocage\nDurant 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.\nIl 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.\nPour 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.\nTout fonctionne parfaitement. Les 11 000 pages prennent entre 5 et 8 minutes pour être créées.\nLa surprise ou le blocage qu'on attendait pas\nBon 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.\nMais là où nous avons été surpris, c'est quand nous avons testé le déploiement sur Vercel.\nUne 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 !\nClairement, 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 !\n\n\n\n\n\n\n39 minutes de temps de déploiement\n\nLe 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.\nLa régénération incrémentale à la rescousse\nC'est alors que la fonctionnalité de régéneration incrementale 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 ou régénérer.\nOn peut utiliser le mode fallback de la fonction getStaticPaths avec la valeur true ou blocking pour afficher une page non générée lors de la phase de build.\nEnsuite, vous donnez un tableau vide comme valeur paths de la fonction getStaticPaths pour ne générer aucune page lors du build. Les pages seront générées à la demande en cas de visite.\nRésultat, un temps de génération entre deux et quatre minutes pour un site statique hybride de 10 991 articles !\n\n\n\n\n\n\n10991 articles\n\n\n\n\n\n\n\n4 minutes de temps de déploiement\n\nCerise sur le gâteau, en utilisant le paramètre revalidate, 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 !\n\n\n\nLa solution retenue\nBien 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.\nNous 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.\nDonc 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 fallback en blocking pour être 100% SEO compatible.\nL'idée de générer au build les X 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.\nFinalement, 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.\nPrototype privé\nLe projet est confidentiel et le code privé. Je ne peux pas fournir les données pour faire des tests.",
      "content_html": "<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>\n<h2 id=\"le-contexte\">Le contexte</h2>\n<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>\n<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>\nIl désire donc basculer une bonne partie des sites actuels sur des sites statiques.  </p>\n<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>\n<h2 id=\"choix-des-generateurs-et-du-cms-headless\">Choix des générateurs et du CMS Headless</h2>\n<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>\n<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>\n<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>\nOn 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>\n<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>\n<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>\n<aside class=\"note note-info\"><h3 id=\"next-js\">Next.js</h3>\n<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>\nUniquement 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>\n<aside class=\"note note-info\"><h3 id=\"nuxt-js\">Nuxt.js</h3>\n<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>\nIl 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>\nRien de confirmé pour le moment, mais la version 3.0 de Nuxt.js devrait proposer une fonctionnalité équivalente.</p></aside>\n<h2 id=\"commencons-avec-next-js\">Commençons avec Next.js</h2>\n<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>\n<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>\n<h2 id=\"generation-des-11-000-pages-et-premier-blocage\">Génération des 11 000 pages et premier blocage</h2>\n<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>\n<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>\n<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>\nTout fonctionne parfaitement. Les 11 000 pages prennent entre 5 et 8 minutes pour être créées.</p>\n<h2 id=\"la-surprise-ou-le-blocage-qu-on-attendait-pas\">La surprise ou le blocage qu'on attendait pas</h2>\n<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>\n<p>Mais là où nous avons été surpris, c'est quand nous avons testé le déploiement sur Vercel.<br>\nUne 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>\n<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>\n<figure>\n<picture title=\"39 minutes de temps de déploiement\">\n<source type=\"image/webp\" srcset=\"/images/post/11000-pages-statiques/39mindeploy.db86559c1af22952417cd298357497f6.webp\" width=\"356\" height=\"74\">\n<source type=\"image/avif\" srcset=\"/images/post/11000-pages-statiques/39mindeploy.db86559c1af22952417cd298357497f6.avif\" width=\"356\" height=\"74\">\n<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;\">\n</picture>\n<figcaption>39 minutes de temps de déploiement</figcaption>\n</figure>\n<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>\n<h2 id=\"la-regeneration-incrementale-a-la-rescousse\">La régénération incrémentale à la rescousse</h2>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"10991 articles\">\n<source type=\"image/webp\" srcset=\"/images/post/11000-pages-statiques/10991articles.d1fa6683eef339c38d45565a0a52064d.webp\" width=\"508\" height=\"143\">\n<source type=\"image/avif\" srcset=\"/images/post/11000-pages-statiques/10991articles.d1fa6683eef339c38d45565a0a52064d.avif\" width=\"508\" height=\"143\">\n<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,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAL3UlEQVR4nOVc6ZrcNg4sHvM5sd//FXf3AeLZ2CMS+4MEUCCpOewcM1l9kVvdapEACoWr7aR//fs/glccAgAih8/o/Xr/NSsnIC0fpLR8JfF694vK4c0rRVguVpl+5tj1ee5+fc2SDMbrgDk8v4nhN/X+EEzs+QjEC/KtMjy3+fr83CfJ4bOXH39p9Rv5E+nroNTVq++XxQ0QEyjzSHkWnPAscPCeswlSShtDJP4R1n0TQ/S7DMIbnj+sOP4k6u16KlAJKTloR4bcKkrWZePoPf1INpAQNaMwpYKmBBLQ90jzzRYO6Y/zNfbrKEK44Pc/zoo0/xMAaco/5ZL4vUQhIjCk9367fPS2GzBEWTE2FRE7+VFjRJR9XKY0zyjsYMX2ZNjblRWSJTL1KEOK1ykY6QzJMZrMZ12fqMfqZCqRfj4eEWPpEZD73LmDsoIgIugMyjQE54XAjgnEMAgBk9KiIJa9D/urovPaQJBdHQdilYEMtRlwLSxUVgbl7Fy0q7873KtX64iutEqeJgXpKyYcA9HRRdC7QHoPoKyOlUiBnBiIhJTTptTdweuL9M0xZDPkLoOCn1MKwCBFPXUNWahmQGxOxaAcKoSbEF6v62INt2/dspg8s0tH74LeO50DJOmyMWS8DiOkPF9THmCkhGwMIWUOYACY+5BTdGZpD2ptLCUZ1IgKTNiHwjHvvTIimT45gJF8w8WAu1716elp1TSCYaEDQVBQeGIgWuvovaEpKH16ri4Bzxk552mQjJzJOIun3QFiDqEgzD37ZIu+rrpHGYYz5Dzk4T03xhEomrFtrSk/rxF02PRguVy6+v379+17KrXT0TfmrwmB0VpHa22cvaG3jta7hS+Yd7onDiDcGNm867k47IB0EUgnINQxplwKitpPwfBQqXvr/tkMansQ2B5+pz7KkOwOVvIZFGW8VYKW/1ZAvn2HLOWiGk5fMxnIhCVAWmu4WkO7/NWA6TLDlrjnT8PnklEye+e4jjkkOkIAJACxhExxtnBSNuea7DQgSg4erns8B4rlwflMzmnq48CmyUDdd0Kx5FePIPW/v/++lajsmSY4x1okCGQaYYBwXQ3XdQ1QrgtXa+itoXWvttQz2QibAnoO67lXL2CwsYbxJ1NPeWzJjZa7aO+Ss+nKDjBYSKFQ9SHHNQfLKa5luekGEAAeUsea9evXRwfDRF5ibM62oX5u7OjdQHi6rgHGBGawpIdk6gYoBkgQ/iZEDSD8QjDD1cKUtrBEeg+VkYdMYgYBw4CY4WgfoSKCQ1ZwtMB6B4Q0WUp0z0316+PjsUiPMZ42SdmWtNxxXbiuNgB5mqC0a+YU9VCP1aWUcU6DcHXjXuTCcjy1z6dXSWCILAzpC0MG4IEhG0vdmxUQDocK0DSjMS7nvLFObbgXJ7Ff4mKh/vbbY+CHl4MTADKcerN6qXrkdV14mkDo63UNQLzxVDAySqkTlMg6qKeIoItfex8YEyLAiV2OYWsDJB28uRxC1uxDrJJsPYIiDohXa3tx4Cwx6wb5Y/UmqF+/fsV68MKlFNSSHZicgTkSsIR+KRhPBohWXMoOq0JKQa3KrgFI8BzzeKEc4PchsCEgAks4qbdZfrPxSLe0g2FGDAak4mFWjZ3WZUA8vGs5n501K/sDGBsgjwsaDkjJBbWOU8NMVoaAAWkTjAFIa8oOqq5msnNnF/Mqfc9VW1s9cgIwjApL+Ouzp5OnBdwDleIJPc+clq1BdeMZIFY5xuTOUwZ9fs1Ja6W4jpm0r6uPj49YG8EcvLmg1opa6wRkJnUA0rtVWAqIJnTtB1TgIoI0QXQQFCAPOco4BkXHIGtixhYGYujq03j9UBmFCqtMQChXuoygXqtZGJa+VG+hEt0bzQCITTn6zpBv374RII50yRmlFvRezLi1d0vCmMpbyHp6Ggm9XSORq8BTeSvz3NFNCDXiReHvukh5CjvcGUdlU1grTg6WNVKM9yN0tq2AAZI9p42vg7zL5aB4X3IGZA9X6nBzlrUCkiGFkqmWbhNeZQiHFw8xc6bEgqpX0qk3FCDNAdpkXm1h2mpM8VgNCzHevDpIMUTo9f14RE8KzbJUb617T6IJeqrUU0KaUeA1gHQNp9Nha+/TJCkhJUFCRpYOkbQLaZuLtupWQ7Pn87jD6/1ClVW2XDRY1NEJoE3g2cvommZ87QGWcvxuBrayZLm7eK4c15jqjcpC7aCCJy9fAaB3gNLmJkfo1hWQNKkZ8keKsY9joysrI1YmcaPkDMxcEZJ5KXiYeajWMuP1ULaLAA3IUpBzR8kZbXp+z4Isgp4zksj0/uxlOPUzPMDz4iDjugb4HGbMrqyjJWSfbxkgkobPQmVISCk6La+5ru0DU0zHOjFy3Ky1VgMkCFc0qVeUWlGnd5dcyFNHt+pVPtByGr2HwPJHLQX1YQJSSmBI753mR9QFT0a0lILH6rzIwKhlyLRUbBr+au17gWAsGVKHPoKqpJDYc0fvBCz95qPeva65nYgRQH8iYJbUT58+EUPggExDllrxUCtKLWZMrrJy9yTYcrYJr0DXGs9ptWYenQeSXUZh4B7pCfe6speYAPKcrZUyZVsazJ0h2j/c9yUbU1LCOtPSxMszM+19HBDNmcnsGNYMZTrNyMIPa0D9/OvnRTjEEUctqEVDTRxPe2fcUEtBq90mvKqoVmvVjDcMOCo1oMswXLlGKCs5o5aM6yq4WkVvnTw6jl7qZLHOo1xh7673UYoz0JUmAy4/OKkBw1TgUARA1lAY1wQxJAxHJZbl9cuXHRAPDRoWfBiYc7ZpL3tie6DfQFYDZjZeOYJ6XQX1qrhqwcNDHdNjKnsTVkAyzcMK/dJ36kfEh40rGEF58nArTHik4/OtWOwgAjINqWEKBgpCIaQA8zobIM4Sb5pUaU2eZkxRpVsse6E5wIHVnBRYJoCITozr/C2l+m8qnbv9daSzTIznz6bmgb1jnQRr+awefwvIej2/rnqJVmQK1AIItxHxdWGbYAO2fvny605fGykffkSaDAEz5G5MAdjEOAwoEyfMkUN6HY0hd8OhB1HmEig8X+P5k4cW/RlZKFbfgHE43KsVE2UXj3LkdjUFlJkGHIAwcIH65fPnnXJrObhMMFOKgMTxtIuo8Xhd4xSyNNytYHDFFYd4OQ4Js5aV3L+cw0L0Zj7o87UH2R5hUE5f2MHgKqsTwzgc1s+ffzXaxaYpeejSyiOMypdYzYpP+aIR9fnld+t11HEqKSmm8xAvDPJSssbMQdHwJSbv84CwcU9NoWbP0xNL5bb96ZDExI5g//rLL58CQrqtAuMGIDC0XxIHJVYc1oZgq/OVZZQsA9Okb17jsjB7szWs8Vc+LIpSN4y45tsOyigxtTwD8AKIR60ICMlVHx7qgY7W+bkRyENdmBgP10QXkxr/NR94SKDnx3zIk2YUZwVkP/lgEM5gvAWUdLhaVzit98xzqvcQ1mSutRz+vnUyjgy7Je/k10PYAylcsSShWZrrq5AsXEGGl/Ti8fcECPwaGyCqoG8QPfmtDDmFr90Sb33OQ9V4XgQ6OrmTg+tohFIwIGveBwdlFWsBI9yzynGJwwQETiAs731NkkHIVJYj71X+Kw5Wk6s+EaByh7sdByDs1rwvANIsS1+sKBO9aNMAAEnmOrEZOwPh1Ru/NyIdvNJz4/s6dmBeAkSPtMCRFuuPenNev0YUB/r0npnxEhDcfyAAc97X1//7jrUG4Pf1Jeli5xpuQPSul1Q/degKxgwtKggIA4AKDQtrJOiWTp8F6u85TsVZTekVDMEOhr3Mkv2ucn/rMW19KHM5d3DFF8E4X0fB3wtDTnLUU+X03LF++4/7x5G0TohnKZwcpp4D5K16/ZXHc6K9GRDgz6U+hy0OUfvJYOxAvGdAnjveNSB3LFlDzz8FDODdA3LPklet9QGB+RCA+J2PZ+C3Hh8AkPHpW5ixrfmBmFLzTwr7Z6n6T8wPrznezpCt7v3DZInb3ADyc2u+fzBr/OcAP3D8yTre2fAjGPdHjvoR9Pqjjf+ewfyhpP6ejo8u/3q86v+X9V6O/wem/A+JNyvco0cnfAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;\">\n</picture>\n<figcaption>10991 articles</figcaption>\n</figure>\n<figure>\n<picture title=\"4 minutes de temps de déploiement\">\n<source type=\"image/webp\" srcset=\"/images/post/11000-pages-statiques/4mindeploy.c523cd79283c87f56c565c1e51905018.webp\" width=\"413\" height=\"72\">\n<source type=\"image/avif\" srcset=\"/images/post/11000-pages-statiques/4mindeploy.c523cd79283c87f56c565c1e51905018.avif\" width=\"413\" height=\"72\">\n<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;\">\n</picture>\n<figcaption>4 minutes de temps de déploiement</figcaption>\n</figure>\n<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>\n<p><div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div></p>\n<h2 id=\"la-solution-retenue\">La solution retenue</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"prototype-prive\">Prototype privé</h2>\n<p>Le projet est confidentiel et le code privé. Je ne peux pas fournir les données pour faire des tests.</p>",
      "authors": [
        {
          "name": "patrickF"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2020/12/08/hebergement-statique-en-france/",
      "url": "https://jamstatic.fr/2020/12/08/hebergement-statique-en-france/",
      "title": "Héberger du statique en France, avec Matthias Dugué et Hubert Sablonnière",
      "summary": "Quelles différences entre les plate-formes françaises et les géants américains du cloud quand il s’agit de déployer des sites Jamstack ?",
      "date_published": "2020-12-08T00:00:00+00:00",
      "date_modified": "2021-09-10T00:00:00+00:00","content_text": "Dans ce deuxième épisode de Génération Statique Frank Taillandier et Arnaud Ligny reçoivent Matthias Dugué, en charge des relations avec les développeurs chez Alwaysdata, et Hubert Sablonnière, développeur web chez Clever Cloud. 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.\n\nL’é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.\nLes 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.\nMatthias 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 (Developer Experience), via l’amélioration des outils en ligne de commande dans un premier temps.\nL’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.\nIl 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.\nHubert 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.\nMatthias 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.\nIl 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.\nLes gros acteurs comme AWS ne sont pas non plus à l’abri de pannes, comme on a pu le voir encore recemment.\nChez Clever Cloud, on peut déjà déployer des fonctions en mode PaaS (Platform as a Service), voire des functions as a service (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.\nArnaud 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.\nCette 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.\nLes 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.\nLe 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 workflow et de l’infrastructure utilisée.",
      "content_html": "<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>\n<iframe src=\"https://anchor.fm/jamstatic/embed/episodes/Hberger-du-statique-en-France-enhc1t\" height=\"174px\" width=\"100%\" frameborder=\"0\" scrolling=\"no\"></iframe>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "podcast"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2020/11/19/jamstack-legere-et-performante/",
      "url": "https://jamstatic.fr/2020/11/19/jamstack-legere-et-performante/",
      "title": "Vers une Jamstack légère et performante, avec Nicolas Goutay",
      "summary": "Passer de Gatsby à Eleventy afin de pouvoir concevoir un site web plus performant et accessible à tous.",
      "date_published": "2020-11-19T00:00:00+00:00","content_text": "Dans ce premier épisode de Génération Statique Frank Taillandier et Arnaud Ligny reçoivent Nicolas Goutay, expert performance web, actuellement développeur chez Orbit, conférencier et organisateur des meetups Jamstack parisiens. 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.\n\nIntroduction\nJamstatic 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.\nPrè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 Slack, nous avons décidé de nous essayer au format podcast, afin de partager toujours plus sur le développement de sites web modernes et performants.\nBataille d'égo de CEO\nLa dernière Jamstack Conf a eu lieu en ligne, on en retiendra surtout la joute verbale entre Matt Biilmann le CEO de Netlify, à l’origine de l’appellation Jamstack, et Matt Mullenweg, le CEO d’Automattic venu défendre WordPress. Richard McManus de The New Stack revient sur cet échange 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.\nL'API de WordPress pour le blog de Gatsby\nGatsby, un des deux frameworks React majeurs qui permet de générer des applications web, a d’ailleurs annoncé avoir finalement opté pour WordPress en mode headless pour son blog. 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.\nLes annonces de la première Next.js conf\nL’autre framework React qui a le vent en poupe c’est Next.js. La première conférence du framework hybride qui permet de faire du statique et du SSR a eu lieu fin octobre.\nBeaucoup d’annonces de la part de Vercel, notamment la mise à disposition d’un outil de monitoring de métriques de performance (les Core Web Vitals) mesurées depuis leur CDN auprès des utilisateurs, sans avoir à ajouter la moindre ligne de JS à son site.\nCes métriques 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.\n\n\n\n\n\n\nLe 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.\n\nÉgalement développé en partenariat avec les équipes de Google, Next.js dispose maintenant d’un composant Image 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.\nDevant la popularité croissante de Next.js, Netlify n’a d’autre choix pour retenir les développeurs que de devoir supporter le SSR et le mode preview via des fonctions lambda. Ils offrent désormais quelques cours en ligne dont un pour apprendre les bases de Next.js. 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.\nGatsby simplifie la génération de pages\nGatsby de son côté n'est pas en reste et continue de progresser, le framework vient d'annoncer une nouvelle API de routing basée sur le système de fichiers… à la Next.js ou Nuxt.js. Votre fichier \/about.js sera servi en tant que \/about\/. Nicolas précise que Gatsby demande un peu plus d'investissement initial mais que le résultat final sera similaire.\nNuxt.js de plus en plus statique\nNuxt.js, est désormais capable de générer un site entièrement statique et permet de requêter le système de fichiers grâce à son API de contenu très simple d’utilisation, avec rechargement à chaud, insertion de composants Vue dans le Markdown, un queryBuilder, une recherche textuelle, et bien plus…\nLe 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.\nStocker 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 Forestry 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.\nDes 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.\nHugo de mieux en mieux outillé pour le JavaScript\nEnfin Hugo, le générateur de loin le plus rapide pour transformer ses fichiers source en site web, continue d'intégrer des outils issus de l'écosystème JavaScript, après la transpilation via Babel, la gestion de dépendances npm, on peut aussi maintenant empaqueter son JavaScript à la vitesse de la lumière avec esbuld. Les développeurs peuvent découper leurs projets en modules réutilisables et bénéficier d'un bundler lui aussi bien plus rapide que webpack ou parcel.\nTailwind 2.0\nLa version 2.0 de Tailwind CSS est sortie.Le site fait peau neuve pour l'occasion, il est développé avec Next.js, déployé sur Vercel avec une recherche propulsée par Algolia.\nEleventy en route vers le million de téléchargements\nDe son côté, mine de rien Eleventy le générateur volontairement simple 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 l'approche plus légére privilégiée pour développer le nouveau site d'Orbit, avec Eleventy, Tailwind CSS et Alpine.js.\nComment ne pas faire subir le coût des frameworks à ses visiteurs ?\nDans son article sur https:\/\/orbit.love\/blog\/towards-a-lightweight-jamstack (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.\nNicolas 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.\nGatsby 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é.\nNicolas 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” (rage clicks) 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é.\nTout 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.\nEven worse, the Washington Post.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. pic.twitter.com\/gfaK676SHo— Josh W. Comeau 🎃 (@JoshWComeau) October 31, 2020\nOn surveillera le First Input Delay 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.\nTout 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.\nMalgré 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.\nLes 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.\nLe 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é.\nLe code du site d'Orbit est open source, vous pouvez donc aller voir comment Nicolas a utilisé Alpine pour ajouter de l'interactivité à certaines parties de l'interface comme la navigation, l'inscription à la newsletter ou les extraits de code.\n\nL'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\n\nMozilla adopte cette approche légère sur https:\/\/extensionworkshop.com\/.\nOn notera que certains sites Eleventy utilisent les Web Components et que GitHub a développé un framework pour embarquer des Web Components dans les applications Ruby on Rails. Là encore, il faudrait dédier une émission complète sur ce sujet. C'est quelque chose à surveiller de près.\nIl est possible d'utiliser Vue avec Eleventy, c'est d'ailleurs ce qu'a fait Zach Leatherman sur netlify.com.\nLe statique moderne est toujours en plein évolution, beaucoup d'outils dans le seul but de produire des sites à la fois modernes, accessibles et performants.\nLa discussion continue sur Slack et Twitter…\nIntervenants\n\nFrank Taillandier, Customer Success Manager chez Forestry.io, initiateur de la communauté jamstatic.fr\nArnaud Ligny, consultant technique web &amp; e-commerce, créateur du générateur de site statique Cecil\nNicolas Goutay, développeur web chez Orbit, organisateur des meetups Jamstack Paris, et de la conférence francophone We Love Speed\n",
      "content_html": "<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>\n<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>\n<h2 id=\"introduction\">Introduction</h2>\n<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>\n<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>\n<h2 id=\"bataille-d-ego-de-ceo\">Bataille d'égo de CEO</h2>\n<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>\n<h2 id=\"l-api-de-wordpress-pour-le-blog-de-gatsby\">L'API de WordPress pour le blog de Gatsby</h2>\n<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>\n<h2 id=\"les-annonces-de-la-premiere-next-js-conf\">Les annonces de la première Next.js conf</h2>\n<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>\n<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>\n<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>\n<figure>\n<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.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n</figure>\n<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>\n<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>\n<h2 id=\"gatsby-simplifie-la-generation-de-pages\">Gatsby simplifie la génération de pages</h2>\n<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>\n<h2 id=\"nuxt-js-de-plus-en-plus-statique\">Nuxt.js de plus en plus statique</h2>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"hugo-de-mieux-en-mieux-outille-pour-le-javascript\">Hugo de mieux en mieux outillé pour le JavaScript</h2>\n<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>\n<h2 id=\"tailwind-2-0\">Tailwind 2.0</h2>\n<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>\n<h2 id=\"eleventy-en-route-vers-le-million-de-telechargements\">Eleventy en route vers le million de téléchargements</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<blockquote>\n<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>\n</blockquote>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"intervenants\">Intervenants</h2>\n<ul>\n<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>\n<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>\n<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>\n</ul>",
      "authors": [
        {
          "name": "podcast"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2020/10/31/comparatif-performance-generateurs-de-site-statique/",
      "url": "https://jamstatic.fr/2020/10/31/comparatif-performance-generateurs-de-site-statique/",
      "title": "Comparaison des temps de compilation des générateurs de site statique",
      "summary": "Comment se comportent les principaux générateurs quand il s'agit de compiler 1, 1 000 ou 64 000 fichiers ?",
      "date_published": "2020-10-31T07:38:15+00:00",
      "date_modified": "2020-10-31T13:38:15+00:00","content_text": "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.\nJekyll 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.\nIl y a tant de générateurs de sites statiques (SSG). 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.\nJe me suis efforcé de faciliter cette décision. Un de mes collègues a construit une fiche d'évaluation du générateur de site statique. 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.\n\n\n\n\n\n\nComparatif des principaux générateurs\n\nTous 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 ».\nIl 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.\nIl ne s'agit pas seulement de déterminer quel générateur est le plus rapide. Hugo 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 !\nIl 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.\nLes tests\nLe 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 :\n\nEleventy\nGatsby\nHugo\nJekyll\nNext\nNuxt\n\nChaque test utilise l'approche et les conditions suivantes :\n\nLa 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.\nLe contenu ne contient pas d'images.\nLes 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.\nLa 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.\nChaque test est un essai à froid. Les caches sont effacés et les fichiers Markdown sont régénérés pour chaque test.\n\nCes tests sont considérés comme des tests de référence. Ils utilisent des fichiers Markdown de base et produisent du HTML non stylisé dans la sortie intégrée.\nEn 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 (généralement en les ralentissant).\nPar 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é).\nOn 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.\nLes deux types de générateurs de sites statiques\nAvant cela, considérons d'abord qu'il existe en réalité deux types de générateurs de sites statiques. Appelons-les basique et avancé.\n\nLes générateurs de base (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).\nLes générateurs avancés offrent quelque chose en plus de la sortie d'un site statique, comme le rendu côté serveur, des fonctions serverless et l'intégration d'un framework. Ils ont tendance à être configurés pour être plus dynamiques par défaut.\n\nJ'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.\n\n\n\nGénérateurs Basiques\nGénérateurs Avancés\n\n\n\n\nEleventy\nGatsby\n\n\nHugo\nNext\n\n\nJekyll\nNuxt\n\n\n\nMon hypothèse\nAppliquons la méthode scientifique à cette approche car la science est amusante (et utile) !\nMon 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.\nPermettez-moi de développer un peu plus cette hypothèse.\nLinéaire et rapide\nHugo 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.\nLent, puis rapide, mais toujours lent\nLes 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.\nLes 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).\nMais, à 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 avancé restera plus loin derrière d'une manière significative.\nDans 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.\nJekyll : le vilain petit canard\nRuby 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.\nAu 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.\n\n\n\n\n\n\nLes résultats auxquels on pourrait s'attendre, Hugo le plus rapide et Next.js le plus lent\n\nLes résultats sont arrivés\nLe code de ces tests se trouve sur GitHub. Il y a aussi un site qui montre les résultats relatifs.\nAprè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 :\n\nBasique : Un seul fichier, pour comparer les temps de génération de base\nPetits sites : 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)\nGrands sites : 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.\n\n\n\n\n\n\n\nPerformance de base, Hugo est largement vainqueur\n\n\n\n\n\n\n\nGénération sur des petits sites (&lt; 1024 fichiers) : Hugo est de loin le plus rapide, Gatsby devient plus lent dès 128 fichiers\n\n\n\n\n\n\n\nGénération de gros sites (entre 1000 et 64000 fichiers): Hugo est de loin le plus rapide, Gatsby est exponentiellement plus lent\n\nSynthèse des résultats\nCertains résultats m'ont surpris, alors que d'autres étaient prévisibles. Voici les points les plus importants :\n\nComme prévu, Hugo est le plus rapide, quelle que soit la taille du site. Ce à quoi je ne m'attendais pas, c'est qu'il loin 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.)\nLes 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 Next est plus rapide que Jekyll avec 32 000 fichiers, et plus rapide que Eleventy et Jekyll avec 64 000 fichiers. Il est également surprenant que Jekyll soit plus rapide que Eleventy avec 64 000 fichiers.\nAucun 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.\nJe 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 Gatsby s'est avéré être le plus lent, produisant la courbe la plus exponentielle.\nBien que cela ne soit pas spécifiquement mentionné dans l'hypothèse de départ, l'échelle des différences était plus grande que je ne l'aurais imaginé. 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.\n\nQu'en conclure ?\nLorsque 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 :\n\nLes 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.\n\nNous sommes d'accord.\nPour résumer : La mise à l'échelle des sites Jamstack est difficile.\nLes 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.\nCe qui compte vraiment, c'est votre niveau de tolérance à l'attente en échange de l'expérience de développement.\nPar 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.\nAu boulot, je développe généralement des sites avec Gatsby (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 micro frontend, en déchargeant le traitement des images, en mettant en place des aperçus de contenu, ainsi que de nombreuses autres optimisations.\nDe mon côté, 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.\nEn fin de compte, il ne s'agit pas seulement 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.\nConclusion\nCe 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.\nQuelles 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 ?\nVoilà les questions auxquelles j'aimerais que vous m'aidiez à répondre. Parlons-en.",
      "content_html": "<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.\nJekyll 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>\n<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>\n<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>\n<figure>\n<picture title=\"Comparatif des principaux générateurs\">\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"les-tests\">Les tests</h2>\n<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>\n<ul>\n<li><a href=\"https://www.11ty.dev\" target=\"_blank\" rel=\"noopener noreferrer\">Eleventy</a></li>\n<li><a href=\"https://www.gatsbyjs.com\" target=\"_blank\" rel=\"noopener noreferrer\">Gatsby</a></li>\n<li><a href=\"https://gohugo.io\" target=\"_blank\" rel=\"noopener noreferrer\">Hugo</a></li>\n<li><a href=\"https://jekyllrb.com\" target=\"_blank\" rel=\"noopener noreferrer\">Jekyll</a></li>\n<li><a href=\"https://nextjs.org\" target=\"_blank\" rel=\"noopener noreferrer\">Next</a></li>\n<li><a href=\"https://nuxtjs.org\" target=\"_blank\" rel=\"noopener noreferrer\">Nuxt</a></li>\n</ul>\n<p>Chaque test utilise l'approche et les conditions suivantes :</p>\n<ul>\n<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>\n<li>Le contenu ne contient pas d'images.</li>\n<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>\n<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>\n<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>\n</ul>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"les-deux-types-de-generateurs-de-sites-statiques\">Les deux types de générateurs de sites statiques</h2>\n<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>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Générateurs Basiques</th>\n<th style=\"text-align: left;\">Générateurs Avancés</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\">Eleventy</td>\n<td style=\"text-align: left;\">Gatsby</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\">Hugo</td>\n<td style=\"text-align: left;\">Next</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\">Jekyll</td>\n<td style=\"text-align: left;\">Nuxt</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"mon-hypothese\">Mon hypothèse</h2>\n<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>\n<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>\n<p>Permettez-moi de développer un peu plus cette hypothèse.</p>\n<h3 id=\"lineaire-et-rapide\">Linéaire et rapide</h3>\n<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>\n<h3 id=\"lent-puis-rapide-mais-toujours-lent\">Lent, puis rapide, mais toujours lent</h3>\n<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>\n<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>\n<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>\n<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>\n<h3 id=\"jekyll-le-vilain-petit-canard\">Jekyll : le vilain petit canard</h3>\n<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>\n<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>\n<figure>\n<picture title=\"Les résultats auxquels on pourrait s&#039;attendre, Hugo le plus rapide et Next.js le plus lent\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Les résultats auxquels on pourrait s'attendre, Hugo le plus rapide et Next.js le plus lent</figcaption>\n</figure>\n<h2 id=\"les-resultats-sont-arrives\">Les résultats sont arrivés</h2>\n<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>\n<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>\n<ul>\n<li><strong>Basique</strong> : Un seul fichier, pour comparer les temps de génération de base</li>\n<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>\n<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>\n</ul>\n<figure>\n<picture title=\"Performance de base, Hugo est largement vainqueur\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Performance de base, Hugo est largement vainqueur</figcaption>\n</figure>\n<figure>\n<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\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<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>\n</figure>\n<figure>\n<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\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<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>\n</figure>\n<h2 id=\"synthese-des-resultats\">Synthèse des résultats</h2>\n<p>Certains résultats m'ont surpris, alors que d'autres étaient prévisibles. Voici les points les plus importants :</p>\n<ul>\n<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>\n<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>\n<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>\n<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>\n<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>\n</ul>\n<h2 id=\"qu-en-conclure\">Qu'en conclure ?</h2>\n<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>\n<blockquote>\n<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>\n</blockquote>\n<p>Nous sommes d'accord.</p>\n<p>Pour résumer : <em>La mise à l'échelle des sites Jamstack est difficile.</em></p>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2020/10/05/la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/",
      "url": "https://jamstatic.fr/2020/10/05/la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/",
      "title": "La Jamstack n'est rapide que si vous y veillez",
      "summary": "Pour en finir avec l'incompréhension selon laquelle votre site statique doit forcément utiliser du JavaScript et des APIs.",
      "date_published": "2020-10-05T00:00:00+00:00","content_text": "Nicolas Hoizey 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 !\nLa Jamstack se présente souvent comme un excellent moyen de fournir des sites performants. C'est même le premier avantage répertorié sur jamstack.wtf, un guide1 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.\nVous avez peut-être vu les diatribes fréquentes d'Alex Russell à propos de Gatsby :\nLooking across the full set of traces, modern Gatsby seems to produce pages that take 2-3x as long as they should to become interactive. This is not OK. Gatsby\/NPM\/React regressively tax access to content.In less generous moments, I'd go as far as to say it's unethical.— Alex Russell (@slightlylate) October 17, 2019\nGatsby 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 présenté. Il est possible de corriger cela, notamment avec ce plugin, et je pense que de bons développeurs React peuvent améliorer les choses, mais cela devrait être le cas par défaut, et non après coup.\nEleventy est très différent, comme Zach Leatherman nous le rappelle dans Eleventy’s New Performance Leaderboard :\n\nEleventy 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 rien qui ralentisse votre site.\n\nLe 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.\nParfois, 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.\nPar exemple, swyx a écrit Clientside Webmentions à propos de l’implémentation de Webmention avec Svelte. Tout article faisant la promotion de Webmention 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.\nPrivilégier le côté serveur\nJe préfère le faire sur le serveur.\nCela permet :\n\nD'appeler l’API webmention.io seulement au moment de générer le site, ce qui devrait être moins fréquent que la consultation des pages par les visiteurs.\nDe mettre en cache le résultat des requêtes à webmention.io et l’horodatage de la dernière, afin que la prochaine requête demande uniquement les nouvelles webmentions.\n\nCela sollicite moins webmention.io, 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.\nPar exemple :\n\nMon 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 à 100 requêtes à Webmention.io avec des réponses peu volumineuses.\nPendant 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 à 3 746 requêtes à Webmention.io avec des réponses volumineuses.\n\nUtiliser la génération côté serveur pour récupérer les Webmentions offre de multiples avantages :\n\nLa performance pour les utilisateurs est largement meilleure, avec du HTML déjà compilé sur le serveur et servi de manière statique.\nBeaucoup moins d’appels d’API, ce qui requiert beaucoup moins de temps de compilation et d’énergie.\nTout le monde devrait savoir qu'Aaron Parecki propose l’impressionnant service webmention.io gratuitement, et la majorité des utilisateurs de Webmention l’utilisent aujourd’hui, et ne pas surcharger son API donne bien meilleure conscience.\n\nAméliorer le côté client, s’il est indispensable\nSi 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.\nMais rappelez-vous que chaque JavaScript ajouté à la page a un coût, donc les quelques Webmentions supplémentaires doivent être vraiment utiles.\nAlors, au lieu de le faire pour chaque page vue :\n\nEssayez d’attendre un peu après la génération du site 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 webmention.io pour le contenu plus ancien, qui reçoit probablement moins de Webmentions, comme l’a fait Aaron Gustafson pour les appels côté serveur dans son plugin Jekyll.\nGardez une trace des appels API, pour un utilisateur, dans le localStorage ou l’IndexDB, 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.\n\nLes appels à l’API uniquement côté client ont parfois plus de sens\nJe 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 :\n\nLes Webmentions à afficher sont les mêmes pour tous les visiteurs.\nNe pas générer les plus récentes n’est sûrement pas un problème.\n\nAlors 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.\nCe que je dis c’est que ça ne devrait pas être le cas par défaut.\nPromouvoir la AJMstack Mstack\nC’est aussi quelque chose que je n’aime pas vraiment dans la tendance actuelle de la Jamstack, promouvoir JavaScript et les API bien plus que le balisage (NDT : « balisage » peut se traduire par « Markup » en anglais).\nVoici pour l’exemple ce que vous pouvez voir sur jamstack.wtf (simplifié) :\n\n\nJamstack version aplatie\n\nComme suggéré par Yann, j’aimerais commencer par utiliser cette meilleure présentation :\n\n\nJamstack version empilée\n\nCela 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).\nMais j’aimerais suggérer cette modification :\n\n\nJavaScript fait la liaison\n\nBien sûr, cela se lit AJMstack au lieu de Jamstack, donc je parie que je n’aurais pas de succès dans la promotion… 🤷‍♂️\nMais au final ça semble plus adéquat, et cela montre que JavaScript fait le lien entre les APIs et le balisage.\nCela 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…\nVoici la Mstack :\n\n\nMstack\n\nAssurez-vous déjà que cette « couche » soit au top, et ensuite améliorez-la avec du JavaScript et des APIs au besoin.\n\n\n\n\nIl existe une version française de ce guide.&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<blockquote>\n<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>\n</blockquote>\n<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>\n<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>\n<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>\n<h2 id=\"privilegier-le-cote-serveur\">Privilégier le côté serveur</h2>\n<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>\n<p>Cela permet :</p>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<p>Par exemple :</p>\n<ul>\n<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>\n<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>\n</ul>\n<p>Utiliser la génération côté serveur pour récupérer les Webmentions offre de multiples avantages :</p>\n<ul>\n<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>\n<li>Beaucoup moins d’appels d’API, ce qui requiert beaucoup moins de temps de compilation et d’énergie.</li>\n<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>\n</ul>\n<h2 id=\"ameliorer-le-cote-client-s-il-est-indispensable\">Améliorer le côté client, s’il est indispensable</h2>\n<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>\n<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>\n<p>Alors, au lieu de le faire pour chaque page vue :</p>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<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>\n<ul>\n<li>Les Webmentions à afficher sont les mêmes pour tous les visiteurs.</li>\n<li>Ne pas générer les plus récentes n’est sûrement pas un problème.</li>\n</ul>\n<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>\n<p>Ce que je dis c’est que <strong>ça ne devrait pas être le cas par défaut</strong>.</p>\n<h2 id=\"promouvoir-la-ajmstack-mstack\">Promouvoir la <del>AJMstack</del> Mstack</h2>\n<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>\n<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>\n<figure>\n<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\">\n<figcaption>Jamstack version aplatie</figcaption>\n</figure>\n<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>\n<figure>\n<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\">\n<figcaption>Jamstack version empilée</figcaption>\n</figure>\n<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>\n<p>Mais j’aimerais suggérer cette modification :</p>\n<figure>\n<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\">\n<figcaption>JavaScript fait la liaison</figcaption>\n</figure>\n<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>\n<p>Mais au final ça semble plus adéquat, et cela montre que JavaScript fait le lien entre les APIs et le balisage.</p>\n<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>\n<p>Voici la <strong>Mstack</strong> :</p>\n<figure>\n<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\">\n<figcaption>Mstack</figcaption>\n</figure>\n<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>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "arnaud"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2020/09/05/tout-savoir-sur-les-modules-hugo/",
      "url": "https://jamstatic.fr/2020/09/05/tout-savoir-sur-les-modules-hugo/",
      "title": "Tout ce que vous devez savoir sur les modules Hugo",
      "summary": "Les modules Hugo permettent d'utiliser des fichiers stockés dans n'importe quel dépôt Git dans vos projets.",
      "date_published": "2020-09-05T11:55:22+00:00",
      "date_modified": "2020-09-05T11:55:22+00:00","content_text": "Les modules Hugo, apparus dans la version 0.56.0, 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.\nCet article vous propose de vous mettre la main à la pâte et après avoir vu comment importer un ou plusieurs modules dans votre site, nous développerons notre propre module!\nInitialiser votre projet en tant que module\nTout est module\nIl est important de comprendre qu'avant d'importer un module Hugo, votre projet doit lui même être un module !\nPour initialiser votre projet en tant que module, vous devez faire reférence à une URL de dépôt Git.\nPartons du principe que votre projet Hugo est déjà sur GitHub à l'adresse https:\/\/github.com\/chez-moi\/mon-depot.\nDans un terminal, à la racine de votre projet, lancez :\nhugo mod init github.com\/chez-moi\/mon-depot\n☝️ Cette commande génère un fichier go.mod à la racine du projet. Il ressemble à quelque chose comme :\nmodule github.com\/chez-moi\/mon-depot\n\ngo 1.15\nUn fichier go.sum est également généré, mais ne nous en préoccupons pas pour le moment.\nImporter un dépôt distant\nPrenons un exemple simple et ajoutons à notre projet les icônes mis à disposition par l'équipe de Bootstrap dans le dépôt https:\/\/github.com\/twbs\/icons.\nLa déclaration se fait dans votre fichier de configuration à l'aide du mot clé module et de sa liste d'imports:\nmodule:\n  imports:\n    - path: github.com\/twbs\/icons\nMaintenant lancez la commande hugo et vous pouvez remarquer que le fichier go.mod comporte une nouvelle ligne:\nmodule github.com\/chez-moi\/mon-depot\n\ngo 1.15\n\nrequire github.com\/twbs\/icons v1.0.0 \/\/ indirect\nC'est bien, mais cela ne dit pas à Hugo ce qu'il doit faire de ces fichiers.\nGrâce à la clé mounts, relative à notre import de Bootstrap, donnons plus de directives à Hugo :\nmodule:\n  imports:\n    - path: github.com\/twbs\/bootstrap\n      mounts:\n        - source: icons\n          target: assets\/icons\nComme pour les imports, on peut utiliser plusieurs mounts, pour le moment nous contenterons d'un seul avec les paramètres suivants:\n\nle paramètre source désigne la location des fichiers dans le dépôt distant. Ici nous voulons juste le réperetoire icons situé à la racine du dépôt.\nle paramètre target désigne l'endroit où Hugo doit monter les fichiers dans notre systéme de fichier Hugo unifié.\n\nUne 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 :\n{{ with resources.Get \"icons\/cart.svg\" }}\n  &lt;div class=\"w-4 fill-current\"&gt;\n    {{ .Content | safeHTML }}\n  &lt;\/div&gt;\n{{ end }}\nEt voilà !\nNous pouvons afficher cette icône de panier SVG sans avoir à la recopier dans notre projet.\nDans l'éventualité où vous voulez personnaliser cette icône de panier nous pouvons compter sur le système de fichiers unifié d'Hugo !\nTout 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 assets\/icons\/cart.svg dans notre projet pour qu'il soit utilisé à la place de l'icône de panier de Bootstrap.\nOu si nous voulions, nous pourrions aussi importer le fichier d'un autre dépôt distant pour une simple icône 🤪 :\n- path: github.com\/refactoringui\/heroicons\n    mounts:\n    - source: src\/solid\/shopping-cart.svg\n      target: assets\/icons\/cart.svg\n- path: github.com\/twbs\/icons\n    mounts:\n    - source: icons\n      target: assets\/icons\n☝️ Ici nous importons deux dépôts, chacun avec son propre point de montage.\nPeu 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.\nMise à jour\nEt 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é v1.0.0 à la fin du require dans le fichier go.mod.\nSi github.com\/twbs\/icons sort une version v1.1.0 et que vous souhaitez faire la mise à jour :\nhugo mod get -u github.com\/twbs\/bootstrap\nCette commande mettra votre fichier go.mod à jour avec la dernière version publiée.\nCibler une version\nSi 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):\nhugo mod get github.com\/twbs\/bootstrap@v3.4.1\nSi vous souhaitez utiliser un commit bien précis, pointez vers son hash avec @ comme ceci :\nhugo mod get github.com\/twbs\/bootstrap@394812b61d4dc80bfb2e090de925ae0dfc4cc29b\nVous devez bien entendu versionner les fichiers go.mod et go.sum afin que tous les collaborateurs du projet utilisent les mêmes versions !\nDévelopper un module en local\nCet article ne couvre pas le développemeent local d'un module, nous vous invitons à lire notre note sur le sujet avant d'embarquer pour le joyeux monde des modules Hugo.\nCréer un module Hugo\nBon 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 contenu !\nEt quel meilleur moyen d'apprendre que de créer nous-mêmes notre propre module ?\nCréons pour l'exemple un petit module d'icônes. Pour cela nous voulons :\n\nImporter les fichiers SVG d'un dépôt distant\nCréer une page qui liste toutes les icônes disponibles\nUtiliser notre propre fichier partiel icon pour faciliter l'affichage de n'importe quelle icône du projet.\n\nCréons d'abord un dossier sur notre ordinateur et nommons-le assets\/hugo-icons pour éviter tout conflit avec un dossier assets\/icons existant.\n1. Les imports\nNous avons tout d'abord besoin de lister les imports des fichiers de notre module dans un fichier config.yaml.\nEn 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 !\nNos 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 :\n# config.yaml\nmodule:\n  imports:\n    - path: github.com\/twbs\/bootstrap\n      mounts:\n        - source: icons\n          target: assets\/hugo-icons\/icons\n2. La page de listing\nPour cela nous avons besoin de deux fichiers :\n\nUn fichier de contenu ;\nUn fichier de modèle pour qu'Hugo puisse effectuer le rendu de notre fichier.\n\nPuisque nous utilisons la directive mounts, 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 :\n\npage\/layout.html\npage\/content.md\n\nMettons à jour notre fichier config.yaml, vous pouveez noter que les paramètres mounts se situent à la racine de notre map module.\nLes points de montage ne sont en effet pas réservés aux seuls imports. Vous pouvez attribuer des points de montage au module en question à l'aide de sa propre directive mounts :\n# config.yaml\nmodule:\n  mounts:\n    - source: page\/index.md\n      target: content\/hugo-icons-listing.md\n      lang: en\n    - source: page\/template.html\n      target: layouts\/_default\/hugo-icons-listing.html\n  imports: [...]\nLe paramètre lang 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.\nNos deux fichiers eux ressemblent à ça :\n# page\/index.md\n---\ntitle: Liste des icônes\nlayout: hugo-icons-listing\n---\n\n{{\/* page\/template.html *\/}}\n{{ define \"main\" }}\n  {{ range resources.Match \"hugo-icons\/icons\/*.svg\" }}\n    &lt;div style=\"fill:currentColor;width:3rem;margin:1rem 0\"&gt;\n    {{ .Content | safeHTML }}\n    &lt;\/div&gt;\n  {{ end }}\n{{ end }}\nNotez qu'ici nous partons du principe que votre modèle baseof.html contient un block main, sans quoi Hugo affichera une erreur.\n3. Le fichier partiel\nPlaçons notre fichier dans partials\/icon.html et déclarons un nouveau point de montage :\n# config.yaml\nmodule:\n  mounts:\n    [...]\n    - source: partials\n      target: layouts\/partials\/hugo-icons\n  imports:\n  [...]\nIci nous choississons de monter notre fichier dans un répertoire nommé pour éviter tout conflit avec un fichier partiel icon existant. Les utilisateurs pourront ainsi appeler {{ partial \"hugo-icons\/icon\" \"cart\" }} en toute sécurité.\nNotre fichier partiel :\n{{\/*\n  icon\n  Affiche l'icône correspondant à la chaîne passée comme contexte\n\n  @author @regisphilibert\n\n  @context String (.)\n\n  @access public\n\n  @example - Go Template\n    {{ partial \"hugo-icons\/icon\" \"cart\" }}\n*\/}}\n{{- with resources.Get (print \"hugo-icons\/icons\/\" .) -}}\n  {{- .Content | safeHTML -}}\n{{- end -}}\nFinitions de notre module\nNotre 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.\nNous utilisons la fonction resources.Match introduite dans Hugo 0.57.0. Quant au montage dans les sous-dossiers, il n'est supporté que depuis Hugo 0.64.0.\nIl nous faut donc indiquer que notre module ne marchera qu'avec une version d'Hugo au moins égale à 0.64.0 ou sinon… patatra !\n# config.yaml\nmodule:\n  hugoVersion:\n    # La version extended (Sass) n'est pas requise\n    extended: false\n    # Il n'y a pas de version maximale\n    max: \"\"\n    # Par contre il y a une version minimale\n    min: \"0.64.0\"\nNotre fichier config.yaml final :\nmodule:\n  hugoVersion:\n    min: \"0.64.0\"\n  mounts:\n    - source: page\/index.md\n      target: content\/hugo-icons-listing.md\n      lang: en\n    - source: page\/template.html\n      target: layouts\/_default\/hugo-icons-listing.html\n    - source: partials\n      target: layouts\/partials\/hugo-icons\n  imports:\n    - path: github.com\/twbs\/icons\n      mounts:\n        - source: icons\n          target: assets\/hugo-icons\/icons\nVous pouvez consulter le dépôt de notre module d'exemple pour cet article.\nPense-bête\nNous avons vu les commandes hugo mod init et hugo mod get -u. Ces deux commandes sont aussi très utiles:\nhugo mod clean\nCette commande va supprimer le cache du module. Je l'utiliss dès que quelque chose de marche pas comme prévu.\nhugo mod tidy\nCette commande supprimera les entrées inutilisées dans votre fichier go.sum.\nConclusion\nLes 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.\nEt 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 publier pour enrichir l'écosystème d'Hugo. :smile:\nRessources complémentaires\n\nDocumentation officielle\nLes modules Hugo pour les nuls\nMaîtriser les modules Hugo\n",
      "content_html": "<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>\n<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>\n<h3 id=\"initialiser-votre-projet-en-tant-que-module\">Initialiser votre projet en tant que module</h3>\n<aside class=\"note note-note\"><p><strong>Tout est module</strong><br>\nIl est important de comprendre qu'avant d'importer un module Hugo, votre projet doit lui même être un module !</p></aside>\n<p>Pour initialiser votre projet en tant que module, vous devez faire reférence à une URL de dépôt Git.</p>\n<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>\n<p>Dans un terminal, à la racine de votre projet, lancez :</p>\n<pre><code class=\"language-bash hljs bash\">hugo mod init github.com/chez-moi/mon-depot</code></pre>\n<p>☝️ Cette commande génère un fichier <code>go.mod</code> à la racine du projet. Il ressemble à quelque chose comme :</p>\n<pre><code class=\"language-go hljs go\">module github.com/chez-moi/mon-depot\n\n<span class=\"hljs-keyword\">go</span> <span class=\"hljs-number\">1.15</span></code></pre>\n<p>Un fichier <code>go.sum</code> est également généré, mais ne nous en préoccupons pas pour le moment.</p>\n<h3 id=\"importer-un-depot-distant\">Importer un dépôt distant</h3>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">module:</span>\n  <span class=\"hljs-attr\">imports:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">github.com/twbs/icons</span></code></pre>\n<p>Maintenant lancez la commande <code>hugo</code> et vous pouvez remarquer que le fichier <code>go.mod</code> comporte une nouvelle ligne:</p>\n<pre><code class=\"language-go hljs go\">module github.com/chez-moi/mon-depot\n\n<span class=\"hljs-keyword\">go</span> <span class=\"hljs-number\">1.15</span>\n\nrequire 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>\n<p>C'est bien, mais cela ne dit pas à Hugo ce qu'il doit faire de ces fichiers.</p>\n<p>Grâce à la clé <code>mounts</code>, relative à notre import de Bootstrap, donnons plus de directives à Hugo :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">module:</span>\n  <span class=\"hljs-attr\">imports:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">github.com/twbs/bootstrap</span>\n      <span class=\"hljs-attr\">mounts:</span>\n        <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">icons</span>\n          <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">assets/icons</span></code></pre>\n<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>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<pre><code class=\"language-html hljs xml\">{{ with resources.Get \"icons/cart.svg\" }}\n  <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>\n    {{ .Content | safeHTML }}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n{{ end }}</code></pre>\n<p>Et voilà !</p>\n<p>Nous pouvons afficher cette icône de panier SVG sans avoir à la recopier dans notre projet.</p>\n<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>\n<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>\n<p>Ou si nous voulions, nous pourrions aussi importer le fichier d'un autre dépôt distant pour une simple icône 🤪 :</p>\n<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>\n    <span class=\"hljs-attr\">mounts:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">src/solid/shopping-cart.svg</span>\n      <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">assets/icons/cart.svg</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">github.com/twbs/icons</span>\n    <span class=\"hljs-attr\">mounts:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">icons</span>\n      <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">assets/icons</span></code></pre>\n<p>☝️ Ici nous importons deux dépôts, chacun avec son propre point de montage.</p>\n<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>\n<h3 id=\"mise-a-jour\">Mise à jour</h3>\n<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>\n<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>\n<pre><code class=\"language-bash hljs bash\">hugo mod get -u github.com/twbs/bootstrap</code></pre>\n<p>Cette commande mettra votre fichier <code>go.mod</code> à jour avec la dernière version publiée.</p>\n<h3 id=\"cibler-une-version\">Cibler une version</h3>\n<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>\n<pre><code class=\"language-bash hljs bash\">hugo mod get github.com/twbs/bootstrap@v3.4.1</code></pre>\n<p>Si vous souhaitez utiliser un commit bien précis, pointez vers son hash avec <code>@</code> comme ceci :</p>\n<pre><code class=\"language-bash hljs bash\">hugo mod get github.com/twbs/bootstrap@394812b61d4dc80bfb2e090de925ae0dfc4cc29b</code></pre>\n<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>\n<aside class=\"note note-note\"><p><strong>Développer un module en local</strong><br>\nCet 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>\n<h2 id=\"creer-un-module-hugo\">Créer un module Hugo</h2>\n<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>\n<p>Et quel meilleur moyen d'apprendre que de créer nous-mêmes notre propre module ?</p>\n<p>Créons pour l'exemple un petit module d'icônes. Pour cela nous voulons :</p>\n<ol>\n<li>Importer les fichiers SVG d'un dépôt distant</li>\n<li>Créer une page qui liste toutes les icônes disponibles</li>\n<li>Utiliser notre propre fichier partiel <code>icon</code> pour faciliter l'affichage de n'importe quelle icône du projet.</li>\n</ol>\n<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>\n<h3 id=\"1-les-imports\">1. Les imports</h3>\n<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>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># config.yaml</span>\n<span class=\"hljs-attr\">module:</span>\n  <span class=\"hljs-attr\">imports:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">github.com/twbs/bootstrap</span>\n      <span class=\"hljs-attr\">mounts:</span>\n        <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">icons</span>\n          <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">assets/hugo-icons/icons</span></code></pre>\n<h3 id=\"2-la-page-de-listing\">2. La page de listing</h3>\n<p>Pour cela nous avons besoin de deux fichiers :</p>\n<ol>\n<li>Un fichier de contenu ;</li>\n<li>Un fichier de modèle pour qu'Hugo puisse effectuer le rendu de notre fichier.</li>\n</ol>\n<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>\n<ul>\n<li><code>page/layout.html</code></li>\n<li><code>page/content.md</code></li>\n</ul>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># config.yaml</span>\n<span class=\"hljs-attr\">module:</span>\n  <span class=\"hljs-attr\">mounts:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">page/index.md</span>\n      <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">content/hugo-icons-listing.md</span>\n      <span class=\"hljs-attr\">lang:</span> <span class=\"hljs-string\">en</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">page/template.html</span>\n      <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">layouts/_default/hugo-icons-listing.html</span>\n  <span class=\"hljs-attr\">imports:</span> <span class=\"hljs-string\">[...]</span></code></pre>\n<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>\n<p>Nos deux fichiers eux ressemblent à ça :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># page/index.md</span>\n<span class=\"hljs-meta\">---</span>\n<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>\n<span class=\"hljs-attr\">layout:</span> <span class=\"hljs-string\">hugo-icons-listing</span>\n<span class=\"hljs-meta\">---</span>\n</code></pre>\n<pre><code class=\"language-html hljs xml\">{{/* page/template.html */}}\n{{ define \"main\" }}\n  {{ range resources.Match \"hugo-icons/icons/*.svg\" }}\n    <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>\n    {{ .Content | safeHTML }}\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  {{ end }}\n{{ end }}</code></pre>\n<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>\n<h3 id=\"3-le-fichier-partiel\">3. Le fichier partiel</h3>\n<p>Plaçons notre fichier dans <code>partials/icon.html</code> et déclarons un nouveau point de montage :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># config.yaml</span>\n<span class=\"hljs-attr\">module:</span>\n  <span class=\"hljs-attr\">mounts:</span>\n    <span class=\"hljs-string\">[...]</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">partials</span>\n      <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">layouts/partials/hugo-icons</span>\n  <span class=\"hljs-attr\">imports:</span>\n  <span class=\"hljs-string\">[...]</span></code></pre>\n<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>\n<p>Notre fichier partiel :</p>\n<pre><code>{{/*\n  icon\n  Affiche l'icône correspondant à la chaîne passée comme contexte\n\n  @author @regisphilibert\n\n  @context String (.)\n\n  @access public\n\n  @example - Go Template\n    {{ partial \"hugo-icons/icon\" \"cart\" }}\n*/}}</code></pre>\n<pre><code class=\"language-html hljs xml\">{{- with resources.Get (print \"hugo-icons/icons/\" .) -}}\n  {{- .Content | safeHTML -}}\n{{- end -}}</code></pre>\n<h3 id=\"finitions-de-notre-module\">Finitions de notre module</h3>\n<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>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># config.yaml</span>\n<span class=\"hljs-attr\">module:</span>\n  <span class=\"hljs-attr\">hugoVersion:</span>\n    <span class=\"hljs-comment\"># La version extended (Sass) n'est pas requise</span>\n    <span class=\"hljs-attr\">extended:</span> <span class=\"hljs-literal\">false</span>\n    <span class=\"hljs-comment\"># Il n'y a pas de version maximale</span>\n    <span class=\"hljs-attr\">max:</span> <span class=\"hljs-string\">\"\"</span>\n    <span class=\"hljs-comment\"># Par contre il y a une version minimale</span>\n    <span class=\"hljs-attr\">min:</span> <span class=\"hljs-string\">\"0.64.0\"</span></code></pre>\n<p>Notre fichier <code>config.yaml</code> final :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">module:</span>\n  <span class=\"hljs-attr\">hugoVersion:</span>\n    <span class=\"hljs-attr\">min:</span> <span class=\"hljs-string\">\"0.64.0\"</span>\n  <span class=\"hljs-attr\">mounts:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">page/index.md</span>\n      <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">content/hugo-icons-listing.md</span>\n      <span class=\"hljs-attr\">lang:</span> <span class=\"hljs-string\">en</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">page/template.html</span>\n      <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">layouts/_default/hugo-icons-listing.html</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">partials</span>\n      <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">layouts/partials/hugo-icons</span>\n  <span class=\"hljs-attr\">imports:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">github.com/twbs/icons</span>\n      <span class=\"hljs-attr\">mounts:</span>\n        <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">source:</span> <span class=\"hljs-string\">icons</span>\n          <span class=\"hljs-attr\">target:</span> <span class=\"hljs-string\">assets/hugo-icons/icons</span></code></pre>\n<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>\n<aside class=\"note note-info\"><h4 id=\"pense-bete\">Pense-bête</h4>\n<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:\n<code>hugo mod clean</code>\nCette commande va supprimer le cache du module. Je l'utiliss dès que quelque chose de marche pas comme prévu.\n<code>hugo mod tidy</code>\nCette commande supprimera les entrées inutilisées dans votre fichier <code>go.sum</code>.</p></aside>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>\n<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>\n<h2 id=\"ressources-complementaires\">Ressources complémentaires</h2>\n<ul>\n<li><a href=\"https://gohugo.io/hugo-modules/\" target=\"_blank\" rel=\"noopener noreferrer\">Documentation officielle</a></li>\n<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>\n<li><a href=\"https://www.hugofordevelopers.com/series/master-hugo-modules/\" target=\"_blank\" rel=\"noopener noreferrer\">Maîtriser les modules Hugo</a></li>\n</ul>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/12/27/webmentions-eleventy/",
      "url": "https://jamstatic.fr/2019/12/27/webmentions-eleventy/",
      "title": "Guide complet des Webmentions avec Eleventy",
      "summary": "Ajoutez les Webmentions à votre site statique Eleventy grâce à ce guide pas à pas.",
      "date_published": "2019-12-27T07:48:32+00:00",
      "date_modified": "2019-12-27T16:05:32+00:00","content_text": "Je suis toujours une grande fan du générateur de site statique Eleventy, et j'étais impatiente de gérer les Webmentions avec.\n\nWebmention 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é.\n— IndieWeb.org\n\nC'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, Indieweb statique 2e partie : utiliser les Webmentions. Il a également crée un starter pour Eleventy, eleventy-webmentions, un modèle de départ avec un support basique des webmentions.\nAlors pourquoi écrire cet article ? Malheureusement pour moi j'ai commencé à développer mon site avec le blog de base pour Eleventy et j'avais déjà terminé quand j'ai découvert eleventy-webmentions. 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.\nLe 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 eleventy-base-blog, 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.\nLe code de cet article est un mélange de l'article de Max Böck, son site perso, du modèle d'amorçage eleventy-webmentions, du site de Zach Leatherman, 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.\nÉtape 1 : s'inscrire sur webmentions.io\nIl 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.\n\nConfigurer IndieAuth de manière à ce que webmention.io sache que vous êtes bien le propriétaire de votre domaine. Suivez les instructions données sur leur site.\nAllez sur webmention.io.\nEntrez l'URL de votre site web dans le champ \"Web Sign-In\" , et cliquez sur \"Sign in\".\n\nSi la validation est réussie, vous devriez être redirigé·e vers le tableau des webmentions où sont affichées deux balises &lt;link&gt; que vous devez insérez dans la balise &lt;head&gt; de votre site web :\n&lt;!-- _includes\/layouts\/base.njk --&gt;\n&lt;link rel=\"webmention\" href=\"https:\/\/webmention.io\/&lt;your.domain&gt;\/webmention\" \/&gt;\n&lt;link rel=\"pingback\" href=\"https:\/\/webmention.io\/&lt;your.domain&gt;\/xmlrpc\" \/&gt;\nVous 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 dotenv pour définir et accéder facilement à vos variables d'environnement :\nnpm install dotenv\nCréez un fichier .env à la racine de votre projet et ajoutez votre token d'API pour webmention.io.\nWEBMENTION_IO_TOKEN=v07r370k3n1c1.\nN'oubliez pas d'ajouter votre fichier .env dans votre fichier .gitignore. Tant que nous y sommes, ajoutons également le dossier _cache qui sera crée lors du premier rapatriement des webmentions :\n_cache\/\n_site\/\nnode_modules\/\n.env\nVous aimeriez probablement récupérer quelques webmentions. Si vous utilisez Twitter, Bridgy 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.\nComment tout ça va marcher\nQuand nous lançons une génération avec NODE_ENV=production, nous allons récupérer les nouvelles webmentions publiées depuis la fois précédente. Celles-ci seront sauvées dans le fichier _cache\/webmentions.json.  Ces mentions proviennent de l'API de webmention.io.\nLors de chaque génération, pour chaque page :\n\nDepuis le cache des webmentions _cache\/webmentions.json, ne garder que les webmentions qui correspondent à l'URL de la page en cours (dans mon cas, celle de l’article de blog).\nFaire appel à la fonction webmentionsByType pour les filtrer par type (par exemple des likes ou des réponses)\nUtiliser la fonction size pour calculer le nombre de mentions par type\nAfficher le total avec le type de mention sous forme d'entête (ex: \"7 réponses\")\nAfficher la liste des mentions de ce type (par exemple sous forme d'avatar avec un lien vers le profil Twitter pour chaque like.)\n\nRécupération des webmentions\nTout d'abord, nous devons ajouter notre nom de domaine en tant que propriété dans notre fichier _data\/metadata.json. Ajoutons-y également l'URL racine qui nous sera utile par la suite :\n\/\/ _data\/metadata.json\n{\n  \/\/...other metadata\n  \"domain\": \"example.com\",\n  \"url\": \"https:\/\/example.com\"\n}\nEnsuite, installons quelques dépendances supplémentaires :\nnpm install lodash node-fetch\nEt mettons à jour notre script de build pour y préciser la variable d'environnement NODE_ENV dans notre package.json :\n\/\/ package.json\n{\n  \/\/ …config\n  \"scripts\": {\n    \"build\": \"NODE_ENV=production npx eleventy\",\n    \/\/ scripts…\n}\nNous 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 :\n\nLire les mentions depuis le cache enregistré dans _cache\/webmentions.json.\nSi notre environnement est production, 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.\nSi notre environnement n’est pas production, retourner les mentions depuis le cache.\n\n\/\/ _data\/webmentions.js\n\/\/ Déclaration des dépendances\nconst fs = require('fs')\nconst fetch = require('node-fetch')\nconst unionBy = require('lodash\/unionBy')\nconst domain = require('.\/metadata.json').domain\n\n\/\/ Charger les variables d'environnement avec `dotenv`\nrequire('dotenv').config()\n\n\/\/ Définir l'emplacement du cache et les paramètres d'appel de l'API\nconst CACHE_FILE_PATH = '_cache\/webmentions.json'\nconst API = 'https:\/\/webmention.io\/api'\nconst TOKEN = process.env.WEBMENTION_IO_TOKEN\n\nasync function fetchWebmentions(since, perPage = 10000) {\n  \/\/ Avertir et s'arrêter là si le nom de domaine et le token d'API ne sont pas définis\n  if (!domain || !TOKEN) {\n    console.warn('&gt;&gt;&gt; Impossible de récupérer les webmentions : domaine ou token manquant')\n    return false\n  }\n\n  let url = `${API}\/mentions.jf2?domain=${domain}&amp;token=${TOKEN}&amp;per-page=${perPage}`\n    if (since) url += `&amp;since=${since}` \/\/ ne récupérer que les nouvelles webmentions\n\n  const response = await fetch(url)\n  if (response.ok) {\n    const feed = await response.json()\n    console.log(`&gt;&gt;&gt; ${feed.children.length} nouvelles webmentions récupérées depuis ${API}`)\n    return feed\n  }\n\n  return null\n}\n\n\/\/ Fusionner les nouvelles webmentions avec celles du cache, unique par id\nfunction mergeWebmentions(a, b) {\n  return unionBy(a.children, b.children, 'wm-id')\n}\n\n\/\/ sauvegarder les webmentions combinnées dans le fichier de cache\nfunction writeToCache(data) {\n  const dir = '_cache'\n  const fileContent = JSON.stringify(data, null, 2)\n  \/\/ créer le dossier de cache s'il n'existe pas déjà\n  if (!fs.existsSync(dir)) {\n    fs.mkdirSync(dir)\n  }\n  \/\/ écrire les données dans le fichier de cache JSON\n  fs.writeFile(CACHE_FILE_PATH, fileContent, err =&gt; {\n    if (err) throw err\n    console.log(`&gt;&gt;&gt; webmentions mise en cache dans ${CACHE_FILE_PATH}`)\n  })\n}\n\n\/\/ Lire le contenu du cache à partir du fichier JSON\nfunction readFromCache() {\n  if (fs.existsSync(CACHE_FILE_PATH)) {\n    const cacheFile = fs.readFileSync(CACHE_FILE_PATH)\n    return JSON.parse(cacheFile)\n  }\n\n  \/\/ Pas de cache trouvé.\n  return {\n    lastFetched: null,\n    children: []\n  }\n}\n\nmodule.exports = async function () {\n  console.log('&gt;&gt;&gt; Lectures des webmentions depuis le cache…');\n\n  const cache = readFromCache()\n\n  if (cache.children.length) {\n    console.log(`&gt;&gt;&gt; ${cache.children.length} webmentions chargées depuis le cache`)\n  }\n\n  \/\/ Ne télécharger les nouvelles webmentions qu'en production\n  if (process.env.NODE_ENV === 'production') {\n    console.log('&gt;&gt;&gt; Vérification de nouvelles webmentions...');\n    const feed = await fetchWebmentions(cache.lastFetched)\n    if (feed) {\n      const webmentions = {\n        lastFetched: new Date().toISOString(),\n        children: mergeWebmentions(cache, feed)\n      }\n\n      writeToCache(webmentions)\n      return webmentions\n    }\n  }\n\n  return cache\n}\nFiltres pour la génération\nMaintenant que nous avons rempli notre cache de webmentions, il nous faut pouvoir l'utiliser. Nous devons pour cela générer les fonctions, les filtres, qu'Eleventy va utiliser pour générer nos fichiers.\nD'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 _11ty\/filters.js :\n\/\/ _11ty\/filters.js\nconst { DateTime } = require(\"luxon\"); \/\/ Déjà présent dans eleventy-base-blog\n\nmodule.exports = {\n  getWebmentionsForUrl: (webmentions, url) =&gt; {\n    return webmentions.children.filter(entry =&gt; entry['wm-target'] === url)\n  },\n  size: (mentions) =&gt; {\n    return !mentions ? 0 : mentions.length\n  },\n  webmentionsByType: (mentions, mentionType) =&gt; {\n    return mentions.filter(entry =&gt; !!entry[mentionType])\n  },\n  readableDateFromISO: (dateStr, formatStr = \"dd LLL yyyy 'at' hh:mma\") =&gt; {\n    return DateTime.fromISO(dateStr).toFormat(formatStr);\n  }\n}\nMaintenant pour pouvoir utiliser ces nouveaux filtres, dans notre fichier .eleventy.js, nous devons boucler sur les clefs de cet objet de filtres, pour ajouter chaque filtre à la configuration d'Eleventy :\n\/\/ .eleventy.js\n\/\/ ...Autres imports\nconst filters = require('.\/_11ty\/filters')\n\nmodule.exports = function(eleventyConfig) {\n  \/\/ Filters\n  Object.keys(filters).forEach(filterName =&gt; {\n    eleventyConfig.addFilter(filterName, filters[filterName])\n  })\n\n  \/\/ Autres configs...\nIci je n'utilise pas de filtre d’assainissement du HTML car j'ai remarqué que les données sont contenues dans un champ text 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.\nAfficher les mentions\nNous 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 _includes\/layouts\/post.njk, j'ajoute une nouvelle section pour les webmentions. Ici, nous déclarons une variable nommée webmentionUrl qui contient l’URL complète de la page, puis nous la passons dans le fichier partiel webmentions.njk :\n&lt;!-- _includes\/layouts\/post.njk --&gt;\n&lt;section&gt;\n  &lt;h2&gt;Webmentions&lt;\/h3&gt;\n  {% set webmentionUrl %}{{ page.url | url | absoluteUrl(site.url) }}{% endset %}\n  {% include 'webmentions.njk' %}\n&lt;\/section&gt;\nNous 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 :\n&lt;!-- _includes\/webmentions.njk --&gt;\n  &lt;!-- Filtrer les webmentions du cache pour n’inclure que celles relatives à l’URL de l’article en cours --&gt;\n  {% set mentions = webmentions | getWebmentionsForUrl(metadata.url + webmentionUrl) %}\n  &lt;!-- Définir les reposts comme des mentions de type `repost-of`  --&gt;\n  {% set reposts = mentions | webmentionsByType('repost-of') %}\n  &lt;!-- Calcul du total de reposts --&gt;\n  {% set repostsSize = reposts | size %}\n  &lt;!-- Définir les likes comme des mentions de type `like-of`  --&gt;\n  {% set likes = mentions | webmentionsByType('like-of') %}\n  &lt;!-- Calcul du total de likes --&gt;\n  {% set likesSize = likes | size %}\n  &lt;!-- Définir les réponses comme des mentions de type `in-reply-to`  --&gt;\n  {% set replies = mentions | webmentionsByType('in-reply-to')  %}\n  &lt;!-- Calcul du total de réponses --&gt;\n  {% set repliesSize = replies | size  %}\nUne 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 ce gist.\nL'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 :\n&lt;!-- _includes\/webmentions.njk --&gt;\n&lt;!-- …définition des variables… --&gt;\n{% if repliesSize &gt; 0 %}\n&lt;div class=\"webmention-replies\"&gt;\n  &lt;h3&gt;{{ repliesSize }} {% if repliesSize == \"1\" %}Reply{% else %}Replies{% endif %}&lt;\/h3&gt;\n\n  {% for webmention in replies %}\n    {% include 'webmention.njk' %}\n  {% endfor %}\n&lt;\/div&gt;\n{% endif %}\nNous 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 readableDateFromISO nous aide à afficher la date de publication dans un format plus sympathique pour les humains, enfin nous affichons le texte de la webmention :\n&lt;!-- _includes\/webmention.njk --&gt;\n&lt;article class=\"webmention\" id=\"webmention-{{ webmention['wm-id'] }}\"&gt;\n  &lt;div class=\"webmention__meta\"&gt;\n    {% if webmention.author %}\n      {% if webmention.author.photo %}\n      &lt;img src=\"{{ webmention.author.photo }}\" alt=\"{{ webmention.author.name }}\" width=\"48\" height=\"48\" loading=\"lazy\"&gt;\n      {% else %}\n      &lt;img src=\"{{ '\/img\/avatar.svg' | url }}\" alt=\"\" width=\"48\" height=\"48\"&gt;\n      {% endif %}\n      &lt;span&gt;\n        &lt;a class=\"h-card u-url\" {% if webmention.url %}href=\"{{ webmention.url }}\" {% endif %} target=\"_blank\" rel=\"noopener noreferrer\"&gt;&lt;strong class=\"p-name\"&gt;{{ webmention.author.name }}&lt;\/strong&gt;&lt;\/a&gt;\n      &lt;\/span&gt;\n    {% else %}\n      &lt;span&gt;\n        &lt;strong&gt;Anonymous&lt;\/strong&gt;\n      &lt;\/span&gt;\n    {% endif %}\n\n    {% if webmention.published %}\n      &lt;time class=\"postlist-date\" datetime=\"{{ webmention.published }}\"&gt;\n        {{ webmention.published | readableDateFromISO }}\n      &lt;\/time&gt;\n    {% endif %}\n  &lt;\/div&gt;\n  &lt;div&gt;\n    {{ webmention.content.text }}\n  &lt;\/div&gt;\n&lt;\/article&gt;\nSautons courageusement dans l’inconnu…\nÇa fonctionne ? Nous allons enfin pouvoir tester. Commencez par lancer la commande npm run build pour générer une liste initiale de webmentions qui sera sauvegardée dans le fichier _cache\/webmentions.json. 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. 😁\nVous pouvez voir le résultat de ma propre implémentation sur mon site. Bon courage ! Dites moi si vous trouvez des anomalies ou des erreurs dans cet article !\nPoursuivez en ajoutant des Microformats\nKeith Grant a un excellent article Ajouter le support de Webmention à un site statique. Lisez la section \"Amélioration à l'aide des Microformats\" pour plus d'explication et d'exemple.\nRessources additionnelles\n\nLa totalité du code source de mon site est sur Github. Il évoluera avec le temps, j'en suis sûre, regardez donc attentivement ce commit qui contient l'ensemble de mes changements pour l'ajout des webmentions.\nComment ajouter le support de dotenv sur Netlify est abordé dans cette réponse sur Stack Overflow.\nComment définir un job cron 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 Programmer des déploiements Netlify avec les GitHub Actions.\n",
      "content_html": "<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>\n<blockquote>\n<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é.\n— <a href=\"https://indieweb.org/Webmention\" target=\"_blank\" rel=\"noopener noreferrer\">IndieWeb.org</a></p>\n</blockquote>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"etape-1-s-inscrire-sur-webmentions-io\">Étape 1 : s'inscrire sur webmentions.io</h2>\n<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>\n<ol>\n<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>\n<li>Allez sur <a href=\"https://webmention.io/\" target=\"_blank\" rel=\"noopener noreferrer\">webmention.io</a>.</li>\n<li>Entrez l'URL de votre site web dans le champ \"Web Sign-In\" , et cliquez sur \"Sign in\".</li>\n</ol>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-comment\">&lt;!-- _includes/layouts/base.njk --&gt;</span>\n<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>\n<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>\n<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>\n<pre><code class=\"language-bash hljs bash\">npm install dotenv</code></pre>\n<p>Créez un fichier <code>.env</code> à la racine de votre projet et ajoutez votre token d'API pour webmention.io.</p>\n<pre><code class=\"language-bash hljs bash\">WEBMENTION_IO_TOKEN=v07r370k3n1c1.</code></pre>\n<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>\n<pre><code class=\"language-bash hljs bash\">_cache/\n_site/\nnode_modules/\n.env</code></pre>\n<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>\n<h2 id=\"comment-tout-ca-va-marcher\">Comment tout ça va marcher</h2>\n<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>\n<p>Lors de chaque génération, pour chaque page :</p>\n<ul>\n<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>\n<li>Faire appel à la fonction <code>webmentionsByType</code> pour les filtrer par type (par exemple des <em>likes</em> ou des réponses)</li>\n<li>Utiliser la fonction <code>size</code> pour calculer le nombre de mentions par type</li>\n<li>Afficher le total avec le type de mention sous forme d'entête (ex: \"7 réponses\")</li>\n<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>\n</ul>\n<h2 id=\"recuperation-des-webmentions\">Récupération des webmentions</h2>\n<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>\n<pre><code class=\"language-json hljs json\"><span class=\"hljs-comment\">// _data/metadata.json</span>\n{\n  <span class=\"hljs-comment\">//...other metadata</span>\n  <span class=\"hljs-attr\">\"domain\"</span>: <span class=\"hljs-string\">\"example.com\"</span>,\n  <span class=\"hljs-attr\">\"url\"</span>: <span class=\"hljs-string\">\"https://example.com\"</span>\n}</code></pre>\n<p>Ensuite, installons quelques dépendances supplémentaires :</p>\n<pre><code class=\"language-bash hljs bash\">npm install lodash node-fetch</code></pre>\n<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>\n<pre><code class=\"language-json hljs json\"><span class=\"hljs-comment\">// package.json</span>\n{\n  <span class=\"hljs-comment\">// …config</span>\n  <span class=\"hljs-attr\">\"scripts\"</span>: {\n    <span class=\"hljs-attr\">\"build\"</span>: <span class=\"hljs-string\">\"NODE_ENV=production npx eleventy\"</span>,\n    <span class=\"hljs-comment\">// scripts…</span>\n}</code></pre>\n<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>\n<ol>\n<li>Lire les mentions depuis le cache enregistré dans <code>_cache/webmentions.json</code>.</li>\n<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>\n<li>Si notre environnement n’est pas <code>production</code>, retourner les mentions depuis le cache.</li>\n</ol>\n<pre><code class=\"language-javascript hljs javascript\"><span class=\"hljs-comment\">// _data/webmentions.js</span>\n<span class=\"hljs-comment\">// Déclaration des dépendances</span>\n<span class=\"hljs-keyword\">const</span> fs = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">'fs'</span>)\n<span class=\"hljs-keyword\">const</span> fetch = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">'node-fetch'</span>)\n<span class=\"hljs-keyword\">const</span> unionBy = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">'lodash/unionBy'</span>)\n<span class=\"hljs-keyword\">const</span> domain = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">'./metadata.json'</span>).domain\n\n<span class=\"hljs-comment\">// Charger les variables d'environnement avec `dotenv`</span>\n<span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">'dotenv'</span>).config()\n\n<span class=\"hljs-comment\">// Définir l'emplacement du cache et les paramètres d'appel de l'API</span>\n<span class=\"hljs-keyword\">const</span> CACHE_FILE_PATH = <span class=\"hljs-string\">'_cache/webmentions.json'</span>\n<span class=\"hljs-keyword\">const</span> API = <span class=\"hljs-string\">'https://webmention.io/api'</span>\n<span class=\"hljs-keyword\">const</span> TOKEN = process.env.WEBMENTION_IO_TOKEN\n\n<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>{\n  <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>\n  <span class=\"hljs-keyword\">if</span> (!domain || !TOKEN) {\n    <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>)\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-literal\">false</span>\n  }\n\n  <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>\n    <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>\n\n  <span class=\"hljs-keyword\">const</span> response = <span class=\"hljs-keyword\">await</span> fetch(url)\n  <span class=\"hljs-keyword\">if</span> (response.ok) {\n    <span class=\"hljs-keyword\">const</span> feed = <span class=\"hljs-keyword\">await</span> response.json()\n    <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>)\n    <span class=\"hljs-keyword\">return</span> feed\n  }\n\n  <span class=\"hljs-keyword\">return</span> <span class=\"hljs-literal\">null</span>\n}\n\n<span class=\"hljs-comment\">// Fusionner les nouvelles webmentions avec celles du cache, unique par id</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">mergeWebmentions</span>(<span class=\"hljs-params\">a, b</span>) </span>{\n  <span class=\"hljs-keyword\">return</span> unionBy(a.children, b.children, <span class=\"hljs-string\">'wm-id'</span>)\n}\n\n<span class=\"hljs-comment\">// sauvegarder les webmentions combinnées dans le fichier de cache</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">writeToCache</span>(<span class=\"hljs-params\">data</span>) </span>{\n  <span class=\"hljs-keyword\">const</span> dir = <span class=\"hljs-string\">'_cache'</span>\n  <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>)\n  <span class=\"hljs-comment\">// créer le dossier de cache s'il n'existe pas déjà</span>\n  <span class=\"hljs-keyword\">if</span> (!fs.existsSync(dir)) {\n    fs.mkdirSync(dir)\n  }\n  <span class=\"hljs-comment\">// écrire les données dans le fichier de cache JSON</span>\n  fs.writeFile(CACHE_FILE_PATH, fileContent, err =&gt; {\n    <span class=\"hljs-keyword\">if</span> (err) <span class=\"hljs-keyword\">throw</span> err\n    <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>)\n  })\n}\n\n<span class=\"hljs-comment\">// Lire le contenu du cache à partir du fichier JSON</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">readFromCache</span>(<span class=\"hljs-params\"></span>) </span>{\n  <span class=\"hljs-keyword\">if</span> (fs.existsSync(CACHE_FILE_PATH)) {\n    <span class=\"hljs-keyword\">const</span> cacheFile = fs.readFileSync(CACHE_FILE_PATH)\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-built_in\">JSON</span>.parse(cacheFile)\n  }\n\n  <span class=\"hljs-comment\">// Pas de cache trouvé.</span>\n  <span class=\"hljs-keyword\">return</span> {\n    <span class=\"hljs-attr\">lastFetched</span>: <span class=\"hljs-literal\">null</span>,\n    <span class=\"hljs-attr\">children</span>: []\n  }\n}\n\n<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>{\n  <span class=\"hljs-built_in\">console</span>.log(<span class=\"hljs-string\">'&gt;&gt;&gt; Lectures des webmentions depuis le cache…'</span>);\n\n  <span class=\"hljs-keyword\">const</span> cache = readFromCache()\n\n  <span class=\"hljs-keyword\">if</span> (cache.children.length) {\n    <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>)\n  }\n\n  <span class=\"hljs-comment\">// Ne télécharger les nouvelles webmentions qu'en production</span>\n  <span class=\"hljs-keyword\">if</span> (process.env.NODE_ENV === <span class=\"hljs-string\">'production'</span>) {\n    <span class=\"hljs-built_in\">console</span>.log(<span class=\"hljs-string\">'&gt;&gt;&gt; Vérification de nouvelles webmentions...'</span>);\n    <span class=\"hljs-keyword\">const</span> feed = <span class=\"hljs-keyword\">await</span> fetchWebmentions(cache.lastFetched)\n    <span class=\"hljs-keyword\">if</span> (feed) {\n      <span class=\"hljs-keyword\">const</span> webmentions = {\n        <span class=\"hljs-attr\">lastFetched</span>: <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Date</span>().toISOString(),\n        <span class=\"hljs-attr\">children</span>: mergeWebmentions(cache, feed)\n      }\n\n      writeToCache(webmentions)\n      <span class=\"hljs-keyword\">return</span> webmentions\n    }\n  }\n\n  <span class=\"hljs-keyword\">return</span> cache\n}</code></pre>\n<h2 id=\"filtres-pour-la-generation\">Filtres pour la génération</h2>\n<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>\n<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>\n<pre><code class=\"language-javascript hljs javascript\"><span class=\"hljs-comment\">// _11ty/filters.js</span>\n<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>\n\n<span class=\"hljs-built_in\">module</span>.exports = {\n  <span class=\"hljs-attr\">getWebmentionsForUrl</span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">webmentions, url</span>) =&gt;</span> {\n    <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)\n  },\n  <span class=\"hljs-attr\">size</span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">mentions</span>) =&gt;</span> {\n    <span class=\"hljs-keyword\">return</span> !mentions ? <span class=\"hljs-number\">0</span> : mentions.length\n  },\n  <span class=\"hljs-attr\">webmentionsByType</span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">mentions, mentionType</span>) =&gt;</span> {\n    <span class=\"hljs-keyword\">return</span> mentions.filter(<span class=\"hljs-function\"><span class=\"hljs-params\">entry</span> =&gt;</span> !!entry[mentionType])\n  },\n  <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> {\n    <span class=\"hljs-keyword\">return</span> DateTime.fromISO(dateStr).toFormat(formatStr);\n  }\n}</code></pre>\n<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>\n<pre><code class=\"language-javascript hljs javascript\"><span class=\"hljs-comment\">// .eleventy.js</span>\n<span class=\"hljs-comment\">// ...Autres imports</span>\n<span class=\"hljs-keyword\">const</span> filters = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">'./_11ty/filters'</span>)\n\n<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>{\n  <span class=\"hljs-comment\">// Filters</span>\n  <span class=\"hljs-built_in\">Object</span>.keys(filters).forEach(<span class=\"hljs-function\"><span class=\"hljs-params\">filterName</span> =&gt;</span> {\n    eleventyConfig.addFilter(filterName, filters[filterName])\n  })\n\n  <span class=\"hljs-comment\">// Autres configs...</span></code></pre>\n<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>\n<h2 id=\"afficher-les-mentions\">Afficher les mentions</h2>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-comment\">&lt;!-- _includes/layouts/post.njk --&gt;</span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section</span>&gt;</span>\n  <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>\n  {% set webmentionUrl %}{{ page.url | url | absoluteUrl(site.url) }}{% endset %}\n  {% include 'webmentions.njk' %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">section</span>&gt;</span></code></pre>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-comment\">&lt;!-- _includes/webmentions.njk --&gt;</span>\n  <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>\n  {% set mentions = webmentions | getWebmentionsForUrl(metadata.url + webmentionUrl) %}\n  <span class=\"hljs-comment\">&lt;!-- Définir les reposts comme des mentions de type `repost-of`  --&gt;</span>\n  {% set reposts = mentions | webmentionsByType('repost-of') %}\n  <span class=\"hljs-comment\">&lt;!-- Calcul du total de reposts --&gt;</span>\n  {% set repostsSize = reposts | size %}\n  <span class=\"hljs-comment\">&lt;!-- Définir les likes comme des mentions de type `like-of`  --&gt;</span>\n  {% set likes = mentions | webmentionsByType('like-of') %}\n  <span class=\"hljs-comment\">&lt;!-- Calcul du total de likes --&gt;</span>\n  {% set likesSize = likes | size %}\n  <span class=\"hljs-comment\">&lt;!-- Définir les réponses comme des mentions de type `in-reply-to`  --&gt;</span>\n  {% set replies = mentions | webmentionsByType('in-reply-to')  %}\n  <span class=\"hljs-comment\">&lt;!-- Calcul du total de réponses --&gt;</span>\n  {% set repliesSize = replies | size  %}</code></pre>\n<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>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-comment\">&lt;!-- _includes/webmentions.njk --&gt;</span>\n<span class=\"hljs-comment\">&lt;!-- …définition des variables… --&gt;</span>\n{% if repliesSize &gt; 0 %}\n<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>\n  <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>\n\n  {% for webmention in replies %}\n    {% include 'webmention.njk' %}\n  {% endfor %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n{% endif %}</code></pre>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-comment\">&lt;!-- _includes/webmention.njk --&gt;</span>\n<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>\n  <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>\n    {% if webmention.author %}\n      {% if webmention.author.photo %}\n      <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>\n      {% else %}\n      <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>\n      {% endif %}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span</span>&gt;</span>\n        <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>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">span</span>&gt;</span>\n    {% else %}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span</span>&gt;</span>\n        <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>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">span</span>&gt;</span>\n    {% endif %}\n\n    {% if webmention.published %}\n      <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>\n        {{ webmention.published | readableDateFromISO }}\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">time</span>&gt;</span>\n    {% endif %}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div</span>&gt;</span>\n    {{ webmention.content.text }}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">article</span>&gt;</span></code></pre>\n<h2 id=\"sautons-courageusement-dans-l-inconnu\">Sautons courageusement dans l’inconnu…</h2>\n<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>\n<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>\n<h2 id=\"poursuivez-en-ajoutant-des-microformats\">Poursuivez en ajoutant des Microformats</h2>\n<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>\n<h2 id=\"ressources-additionnelles\">Ressources additionnelles</h2>\n<ul>\n<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>\n<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>\n<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>\n</ul>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/12/22/partiels-fonction-hugo/",
      "url": "https://jamstatic.fr/2019/12/22/partiels-fonction-hugo/",
      "title": "Des fonctions dans nos partiels Hugo !",
      "summary": "Maintenant que les partiels peuvent retourner tout type de donnée, nous pouvons écrire des fonctions réutilisables dans nos différents modèles de page !",
      "date_published": "2019-12-22T09:36:56+00:00",
      "date_modified": "2019-12-22T15:21:43+00:00","content_text": "\n\n\n\n\n\nDes fonctions dans nos partiels Hugo\n\nComme nous l'avons déjà vu dans différents articles dont celui sur la mise en cache des fichiers partiels, jusqu'ici le moteur de templating 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.\nTout a changé à partir de la version 0.55.0 d'Hugo qui a introduit l'instruction return dans l'API de la fonction partial ! Les partiels sont tout à coup devenus des fonctions réutilisables qui peuvent être appelées par des fichiers de modèles plus conventionnels.\nDans cet article, nous aborderons les bases de cette fonctionnalité avant de nous plonger dans des cas d'utilisation bien précis.\nAvant return\nAvant l'arrivée de l'instruction return, 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:\n# layouts\/partial\/FormatURL.html\n{{- $S3Domain := site.Params.s3Domain -}}\n{{- printf \"%s\/%s\" $S3Domain . | safeHTMLAttr -}}\n# layout\/_default\/single.html\n{{- with .Params.image -}}\n&lt;img src=\"{{ partial \"FormatURL.html\" . }}\" \/&gt;\n{{ end }}\nIci la notation {{- -}} 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.\nOn pourrait même aller plus loin et afficher des données au format JSON à l'appel du partiel :\n# layouts\/partial\/GetSEOData.html\n{{- with . -}}\n  {{- $title := .Title -}}\n  {{- $description := .Summary -}}\n  {{- with .Params.seo.title -}}\n    {{- $title = . -}}\n  {{- end -}}\n  {{- with .Params.seo.description -}}\n    {{- $description = . -}}\n  {{- end -}}\n  {{- dict \"title\" $title \"description\" $description | jsonify -}}\n{{- end -}}\n\n# layouts\/partial\/head.html\n{{ $seo := partial \"GetSEOData.html\" . | transform.Unmarshal }}\n{{ with $seo }}\n  # seo tags...\n{{ end }}\nMais 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.\nIl n'y avait aucun moyen de facilement retourner un objet complexe comme une page ou un fichier, encore moins une collection de pages.\nPuis vint Hugo 0.55.0 , l'instruction return et les partiels de fonction!\nQuelques points à retenir\nAvant de nous plonger dans le code, il y a certaines choses à faire et à ne pas faire que nous devons passer en revue.\n🚫 Pas de return dans les clauses\n{{ if gt .Params.temperature 70 }}\n  {{ return 😎 }}\n{{ end }}\nIci ☝️ la valeur .Params.temperature sera ignorée et c'est 😎 qui sera systématiquement retourné. Un partiel retournera simplement ce qui suit une instruction return et ce où qu'elle soit positionnée dans le code.\n{{ with .Params.temperature }}\n  {{ return . }}\n{{ end }}\nMême chose ici ☝️, return doit se trouver à la racine et ne peut pas être appelé dans une instruction imbriquée.\n🚫 Pas de multiples instructions return\n{{ if gt .Params.temperature 70 }}\n  {{ return 😎 }}\n{{ else }}\n  {{ return ⛸️ }}\n{{ end }}\nCet exemple ne marchera pas, car les retours multiples ne sont pas supportés.\n👍 Une unique variable retournée\nOn tire donc des exemples précédents la règle générique : une variable retournée unique à la racine.\nLe meilleur moyen de se conformer à cette règle est de porter notre attention sur l'unique variable retournée.\nLa variable retournée c'est votre base de départ, celle que vous allez manipuler et éventuellement retourner.\n\n🍽️ Affecter une valeur de départ,\n🔪 la travailler,\n💁‍♂️ et la retourner à la fin.\n\n{{ $emoji := ⛸️ }}\n{{ if gt .Params.temperature 70 }}\n  {{ $emoji = 😎}}\n{{ else if gt .Params.temperature 100 }}\n  {{ $emoji = 🥵}}\n{{ end }}\n{{ return $emoji }}\nQuel 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 return.\n{{&lt; notice info &gt;}}\nTout comme pour if, with, range et leurs amis, ce qui suit return n'a pas besoin d'être entre parenthèses.\n{{ return gt .Params.temperature 50 }}\n☝️ Cette notation est valide et retournera un booléen.\n{{&lt; \/notice &gt;}}\n🤙 Appeler nos partiels de fonction\nComme pour n'importe quel autre partiel on écrit :\nEmoji: {{ partial \"emoji.html\" . }}\nLà où ça devient intéressant c'est qu'on peut stocker la valeur retournée !\n{{ $emoji := partial \"emoji.html\" . }}\n📏 Quelques conventions\nDu moins celles que j'ai adoptées…\nPour 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 layouts\/partials\/func\/. 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.\n{{ partial \"func\/emoji.html\" . }}\nHugo part du principe que par défaut l'extension de votre fichier partiel est .html. Vu que j'utilise toujours l'extension html pour mes fichiers partiels, je peux omettre de l'écrire et ainsi gagner cinq précieux caractères :\n{{ partial \"func\/emoji\" . }}\nEnfin, j'aime bien utiliser la casse CamelCase ainsi qu'un verbe autant que possible :\n{{ partial \"func\/GetEmoji\" . }}\nEt voilà mon code Hugo est tout beau :\n{{ with partial \"func\/GetEmoji\" . }}\n  Emoji: {{ . }}\n{{ end }}\nCoder nos partiels de fonction\nBien, 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 : lister les termes des taxonomies de nos projets Hugo de manière efficace.\nCette 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 :\n---\ntitle: Une nuit au Louvre\ntags:\n  - Art\n  - Paris\n  - Musée\n---\n\nPour 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 .GetPage ou .Site.Taxonomies.\nCe serait bien si ce travail pénible pouvait être fait dans un partiel de fonction réutilisable et non dans les fichiers de modèles de nos contenus.\nQuant à l'appel de ce type de partiel, il devrait être aussi concis que cela :\n{{ range .Params.tags }}\n  {{ with partial \"func\/GetTagPage\" . }}\n    &lt;a href=\"{{ .RelPermalink }}\"&gt;{{ .Title }}&lt;\/a&gt;\n  {{ end }}\n{{ end }}\n🙌 ⌨️ C'est parti\n{{\/* 1. *\/}}\n{{ $tag := false }}\n{{\/* 2. *\/}}\n{{ with index site.Taxonomies.tags (urlize .) }}\n  {{\/* 3. *\/}}\n  {{ with .Page }}\n    {{\/* 4. *\/}\n    {{ $tag = . }}\n  {{ end }}\n{{ end }}\n\n{{\/* 5. *\/}\n{{ return $tag }}\n\nNous commençons par initialiser notre variable retournée à sa valeur par défaut\nsite.Taxonomies.tags retourne une collection de tous les tags du site avec leur objet .Page. Le point représente le contexte de notre partiel, ici le nom de notre tag, que nous transformons en URL pour correspondre à sa clef dans site.Taxonomies.tags.\nNous avons de fortes chances de tomber sur une .Page, mais with ajoute une vérification supplémentaire et nous permet en plus de changer de contexte.\nNous stockons le tag de la page dans notre variable retournée.\n🎉\n\n👍 Beau boulot !\nEt pour les autres taxonomies comme les categories? Hors de question de copier\/coller tout cela dans un nouveau partiel pour remplacer site.Taxonomies.tags par site.Taxonomies.categories. 🙅‍♀️\nNous voulons pouvoir écrire ceci :\n{{ range .Params.categories }}\n  {{ with partial \"func\/GetTermPage\" (dict \"taxonomy\" \"categories\" \"term\" .) }}\n    &lt;a href=\"{{ .RelPermalink }}\"&gt;{{ .Title }}&lt;\/a&gt;\n  {{ end }}\n{{ end }}\nGestion des arguments\nJusqu'à 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.\nNous mettons donc notre partiel à jour :\n{{ $return := false }}\n\n{{\/* 1. *\/}}\n{{ $taxonomy := \"tags\" }}\n{{ with .taxonomy }}\n  {{ $taxonomy = . }}\n{{ end }}\n\n{{\/* 2. *\/}}\n{{ with $term := .term }}\n  {{ with index site.Taxonomies $taxonomy }}\n    {{ with index . (urlize $term) }}\n      {{ with .Page }}\n        {{ $return = . }}\n      {{ end }}\n    {{ end }}\n  {{ end }}\n{{ end }}\n\n{{\/* 3. *\/}}\n{{ return $return }}\n\nMaintenant que nous passons un argument term, nommer notre variable retournée $term pourrait prêter à confusion. Appelons la maintenant $return pour bien marquer que c'est la variable retournée.\nSi aucun argument .term n'est présent, la variable retournée devrait rester vide. Avant d'aller plus loin, nous faisons appel à with pour nous assurer que .term 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 !\n🎉\n\nMise en cache !\nOK 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 .URL et .Name. De cette façon, si je veux passer de .RelPermalink à .Permalink dans le futur, je peux le faire dans ma variable retournée plutôt que dans chaque fichier de modèle où je souhaite afficher ces liens.\nC'est l'occasion idéale de voir comment appeler un partiel de fonction depuis un partiel de fonction et mettre en cache sa valeur. :sweat_smile:\n#layout\/partials\/func\/GetTags.html\n{{\/* 1. *\/}}\n{{ $return := slice }}\n{{\/* 2. *\/}}\n{{ with .Params.tags }}\n  {{ range . }}\n    {{\/* 3. *\/}}\n    {{ with partialCached \"func\/GetTerm\" (dict \"taxonomy\" \"tags\" \"term\" .) \"tags\" . }}\n      {{\/* 4. *\/}}\n      {{ $tag := dict \"URL\" .RelPermalink \"Name\" .Title }}\n      {{\/* 5. *\/}}\n      {{ $return := $return | append $tag }}\n    {{ end }}\n  {{ end }}\n{{ end }}\n\n{{\/* 6. *\/}}\n{{ return $return }}\n\nNous voulons être sûrs de pouvoir parcourir la valeur de notre variable retournée à l'aide de la fonction range. Afin de nous assurer de retourner un tableau (slice), nous initialisons notre variable avec un tableau vide.\nLa fonction range 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 with, sauf si vous êtes vraiment sûrs de retourner un tableau.\nNous 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.\nNous sauvegardons le tout dans un tableau associatif à des fins de lisibilité. Puisque notre $tag est déclaré dans le contexte de notre boucle range, il ne pourra pas entrer en conflit avec un autre $tag comme par exemple le prochain tag de la liste.\nNous utilisons la fonction append pour ajouter notre tableau associatif $tag au tableau que nous retournons.\n🎉\n\nMaintenant dans notre modèle nous pouvons écrire :\n# layouts\/_default\/single.html\n{{\/* 1. *\/}}\n{{ range partial \"func\/GetTags\" $ }}\n  {{\/* 2. *\/}}\n  &lt;a href=\"{{ .URL }}\"&gt;{{ .Name }}&lt;\/a&gt;\n{{ end }}\n\nNous 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 range sans problème.\nNous pouvons maintenant utiliser les clefs personnalisées de nos tableaux associatifs.\nC'est tout !\n\nDes améliorations possibles ?\nBien entendu ! Nous pourrions :\n\nExclure certains tags du tableau retourné par la fonction GetTags\nTransformer GetTags en GetTerms, afin de pouvoir l'utiliser pour n'importe quelle taxonomie.\nTrouver la bonne variante de notre partiel de fonction GetTags et utiliser partialCached.\nDévelopper bien plus de partiels de fonction pour répondre à d'autres besoins !\n\nConclusion\nAprè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.\nEt 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 GetTags 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 GetTerm !\nAvec ses partiels de fonction, 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 !\nEst-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 ?\nSi 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, laissez un commentaire ou un bout de code !",
      "content_html": "<figure>\n<picture title=\"Des fonctions dans nos partiels Hugo\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Des fonctions dans nos partiels Hugo</figcaption>\n</figure>\n<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>\n<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>\n<p>Dans cet article, nous aborderons les bases de cette fonctionnalité avant de nous plonger dans des cas d'utilisation bien précis.</p>\n<h3 id=\"avant-return\"><strong>Avant <code>return</code></strong></h3>\n<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>\n<pre><code class=\"language-go-html-template hljs go\"># layouts/partial/FormatURL.html\n{{- $S3Domain := site.Params.s3Domain -}}\n{{- printf <span class=\"hljs-string\">\"%s/%s\"</span> $S3Domain . | safeHTMLAttr -}}\n# layout/_default/single.html\n{{- with .Params.image -}}\n&lt;img src=<span class=\"hljs-string\">\"{{ partial \"</span>FormatURL.html<span class=\"hljs-string\">\" . }}\"</span> /&gt;\n{{ end }}</code></pre>\n<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>\n<p>On pourrait même aller plus loin et afficher des données au format JSON à l'appel du partiel :</p>\n<pre><code class=\"language-go-html-template hljs go\"># layouts/partial/GetSEOData.html\n{{- with . -}}\n  {{- $title := .Title -}}\n  {{- $description := .Summary -}}\n  {{- with .Params.seo.title -}}\n    {{- $title = . -}}\n  {{- end -}}\n  {{- with .Params.seo.description -}}\n    {{- $description = . -}}\n  {{- end -}}\n  {{- dict <span class=\"hljs-string\">\"title\"</span> $title <span class=\"hljs-string\">\"description\"</span> $description | jsonify -}}\n{{- end -}}\n\n# layouts/partial/head.html\n{{ $seo := partial <span class=\"hljs-string\">\"GetSEOData.html\"</span> . | transform.Unmarshal }}\n{{ with $seo }}\n  # seo tags...\n{{ end }}</code></pre>\n<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.\nIl n'y avait aucun moyen de facilement retourner un objet complexe comme une page ou un fichier, encore moins une collection de pages.</p>\n<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>\n<h2 id=\"quelques-points-a-retenir\"><strong>Quelques points à retenir</strong></h2>\n<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>\n<h3 id=\"rџљ-pas-de-return-dans-les-clauses\"><strong>🚫 Pas de <code>return</code> dans les clauses</strong></h3>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">if</span> gt .Params.temperature <span class=\"hljs-number\">70</span> }}\n  {{ <span class=\"hljs-keyword\">return</span> 😎 }}\n{{ end }}</code></pre>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ with .Params.temperature }}\n  {{ <span class=\"hljs-keyword\">return</span> . }}\n{{ end }}</code></pre>\n<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>\n<h3 id=\"rџљ-pas-de-multiples-instructions-return\">🚫 Pas de multiples instructions <code>return</code></h3>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">if</span> gt .Params.temperature <span class=\"hljs-number\">70</span> }}\n  {{ <span class=\"hljs-keyword\">return</span> 😎 }}\n{{ <span class=\"hljs-keyword\">else</span> }}\n  {{ <span class=\"hljs-keyword\">return</span> ⛸️ }}\n{{ end }}</code></pre>\n<p>Cet exemple ne marchera pas, car les retours multiples ne sont pas supportés.</p>\n<h3 id=\"rџ-Ќ-une-unique-variable-retournG-c-e\">👍 Une unique variable retournée</h3>\n<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>\n<p>Le meilleur moyen de se conformer à cette règle est de porter notre attention sur l'unique <em>variable retournée</em>.</p>\n<p>La <em>variable retournée</em> c'est votre base de départ, celle que vous allez manipuler et éventuellement retourner.</p>\n<ol>\n<li>🍽️ Affecter une valeur de départ,</li>\n<li>🔪 la travailler,</li>\n<li>💁‍♂️ et la retourner à la fin.</li>\n</ol>\n<pre><code class=\"language-go-html-template hljs go\">{{ $emoji := ⛸️ }}\n{{ <span class=\"hljs-keyword\">if</span> gt .Params.temperature <span class=\"hljs-number\">70</span> }}\n  {{ $emoji = 😎}}\n{{ <span class=\"hljs-keyword\">else</span> <span class=\"hljs-keyword\">if</span> gt .Params.temperature <span class=\"hljs-number\">100</span> }}\n  {{ $emoji = 🥵}}\n{{ end }}\n{{ <span class=\"hljs-keyword\">return</span> $emoji }}</code></pre>\n<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>\n<p>{{&lt; notice info &gt;}}\nTout 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>\n<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>\n<p>☝️ Cette notation est valide et retournera un booléen.\n{{&lt; /notice &gt;}}</p>\n<h3 id=\"YoYadShch-appeler-nos-partiels-de-fonction\">🤙 Appeler nos partiels de fonction</h3>\n<p>Comme pour n'importe quel autre partiel on écrit :</p>\n<pre><code class=\"language-go-html-template hljs go\">Emoji: {{ partial <span class=\"hljs-string\">\"emoji.html\"</span> . }}</code></pre>\n<p>Là où ça devient intéressant c'est qu'on peut stocker la valeur retournée !</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ $emoji := partial <span class=\"hljs-string\">\"emoji.html\"</span> . }}</code></pre>\n<h3 id=\"rџ-Џ-quelques-conventions\">📏 Quelques conventions</h3>\n<p>Du moins celles que j'ai adoptées…</p>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"func/emoji.html\"</span> . }}</code></pre>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"func/emoji\"</span> . }}</code></pre>\n<p>Enfin, j'aime bien utiliser la casse <em>CamelCase</em> ainsi qu'un verbe autant que possible :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"func/GetEmoji\"</span> . }}</code></pre>\n<p><strong>Et voilà mon code Hugo est tout beau :</strong></p>\n<pre><code class=\"language-go-html-template hljs go\">{{ with partial <span class=\"hljs-string\">\"func/GetEmoji\"</span> . }}\n  Emoji: {{ . }}\n{{ end }}</code></pre>\n<h2 id=\"coder-nos-partiels-de-fonction\">Coder nos partiels de fonction</h2>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<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>\n<span class=\"hljs-attr\">tags:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">Art</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">Paris</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">Musée</span>\n<span class=\"hljs-meta\">---</span>\n</code></pre>\n<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>\n<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>\n<p>Quant à l'appel de ce type de partiel, il devrait être aussi concis que cela :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">range</span> .Params.tags }}\n  {{ with partial <span class=\"hljs-string\">\"func/GetTagPage\"</span> . }}\n    &lt;a href=<span class=\"hljs-string\">\"{{ .RelPermalink }}\"</span>&gt;{{ .Title }}&lt;/a&gt;\n  {{ end }}\n{{ end }}</code></pre>\n<p><strong>🙌 ⌨️ C'est parti</strong></p>\n<pre><code class=\"language-go-html-template hljs go\">{{<span class=\"hljs-comment\">/* 1. */</span>}}\n{{ $tag := <span class=\"hljs-literal\">false</span> }}\n{{<span class=\"hljs-comment\">/* 2. */</span>}}\n{{ with index site.Taxonomies.tags (urlize .) }}\n  {{<span class=\"hljs-comment\">/* 3. */</span>}}\n  {{ with .Page }}\n    {{<span class=\"hljs-comment\">/* 4. */</span>}\n    {{ $tag = . }}\n  {{ end }}\n{{ end }}\n\n{{<span class=\"hljs-comment\">/* 5. */</span>}\n{{ <span class=\"hljs-keyword\">return</span> $tag }}</code></pre>\n<ol>\n<li>Nous commençons par initialiser notre <em>variable retournée</em> à sa valeur par défaut</li>\n<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>\n<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>\n<li>Nous stockons le tag de la page dans notre <em>variable retournée</em>.</li>\n<li>🎉</li>\n</ol>\n<p><strong>👍 Beau boulot !</strong></p>\n<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>\n<p>Nous voulons pouvoir écrire ceci :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">range</span> .Params.categories }}\n  {{ 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> .) }}\n    &lt;a href=<span class=\"hljs-string\">\"{{ .RelPermalink }}\"</span>&gt;{{ .Title }}&lt;/a&gt;\n  {{ end }}\n{{ end }}</code></pre>\n<h2 id=\"gestion-des-arguments\">Gestion des arguments</h2>\n<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>\n<p>Nous mettons donc notre partiel à jour :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ $<span class=\"hljs-keyword\">return</span> := <span class=\"hljs-literal\">false</span> }}\n\n{{<span class=\"hljs-comment\">/* 1. */</span>}}\n{{ $taxonomy := <span class=\"hljs-string\">\"tags\"</span> }}\n{{ with .taxonomy }}\n  {{ $taxonomy = . }}\n{{ end }}\n\n{{<span class=\"hljs-comment\">/* 2. */</span>}}\n{{ with $term := .term }}\n  {{ with index site.Taxonomies $taxonomy }}\n    {{ with index . (urlize $term) }}\n      {{ with .Page }}\n        {{ $<span class=\"hljs-keyword\">return</span> = . }}\n      {{ end }}\n    {{ end }}\n  {{ end }}\n{{ end }}\n\n{{<span class=\"hljs-comment\">/* 3. */</span>}}\n{{ <span class=\"hljs-keyword\">return</span> $<span class=\"hljs-keyword\">return</span> }}</code></pre>\n<ol>\n<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>\n<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>\n<li>🎉</li>\n</ol>\n<h2 id=\"mise-en-cache\">Mise en cache !</h2>\n<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>\n<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>\n<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>\n{{<span class=\"hljs-comment\">/* 1. */</span>}}\n{{ $<span class=\"hljs-keyword\">return</span> := slice }}\n{{<span class=\"hljs-comment\">/* 2. */</span>}}\n{{ with .Params.tags }}\n  {{ <span class=\"hljs-keyword\">range</span> . }}\n    {{<span class=\"hljs-comment\">/* 3. */</span>}}\n    {{ 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> . }}\n      {{<span class=\"hljs-comment\">/* 4. */</span>}}\n      {{ $tag := dict <span class=\"hljs-string\">\"URL\"</span> .RelPermalink <span class=\"hljs-string\">\"Name\"</span> .Title }}\n      {{<span class=\"hljs-comment\">/* 5. */</span>}}\n      {{ $<span class=\"hljs-keyword\">return</span> := $<span class=\"hljs-keyword\">return</span> | <span class=\"hljs-built_in\">append</span> $tag }}\n    {{ end }}\n  {{ end }}\n{{ end }}\n\n{{<span class=\"hljs-comment\">/* 6. */</span>}}\n{{ <span class=\"hljs-keyword\">return</span> $<span class=\"hljs-keyword\">return</span> }}</code></pre>\n<ol>\n<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>\n<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>\n<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>\n<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>\n<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>\n<li>🎉</li>\n</ol>\n<p>Maintenant dans notre modèle nous pouvons écrire :</p>\n<pre><code class=\"language-go-html-template hljs go\"># layouts/_default/single.html\n{{<span class=\"hljs-comment\">/* 1. */</span>}}\n{{ <span class=\"hljs-keyword\">range</span> partial <span class=\"hljs-string\">\"func/GetTags\"</span> $ }}\n  {{<span class=\"hljs-comment\">/* 2. */</span>}}\n  &lt;a href=<span class=\"hljs-string\">\"{{ .URL }}\"</span>&gt;{{ .Name }}&lt;/a&gt;\n{{ end }}</code></pre>\n<ol>\n<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>\n<li>Nous pouvons maintenant utiliser les clefs personnalisées de nos tableaux associatifs.</li>\n<li>C'est tout !</li>\n</ol>\n<h2 id=\"des-ameliorations-possibles\">Des améliorations possibles ?</h2>\n<p>Bien entendu ! Nous pourrions :</p>\n<ul>\n<li>Exclure certains tags du tableau retourné par la fonction <code>GetTags</code></li>\n<li>Transformer <code>GetTags</code> en <code>GetTerms</code>, afin de pouvoir l'utiliser pour n'importe quelle taxonomie.</li>\n<li>Trouver la bonne variante de notre <em>partiel de fonction</em> <code>GetTags</code> et utiliser <code>partialCached</code>.</li>\n<li>Développer bien plus de partiels de fonction pour répondre à d'autres besoins !</li>\n</ul>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>\n<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>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "regis"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/12/10/jamstack-pour-les-clients/",
      "url": "https://jamstatic.fr/2019/12/10/jamstack-pour-les-clients/",
      "title": "Jamstack pour les clients",
      "summary": "Comment communiquer clairement et efficacement les avantages de la Jamstack à vos prospects et vos clients ?",
      "date_published": "2019-12-10T12:54:24+00:00",
      "date_modified": "2019-12-14T14:24:18+00:00","content_text": "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.\nDe 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.\nC'est génial quand vous faites partie d'une équipe de développement web. Vous utilisez leurs explications comme fil directeur et faites des recherches sur le sujet.\nMais ça ne marchera pas avec vos clients.\nDonc, 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.\nLes bénéfices de la Jamstack\nJe ne vais pas vous embêter avec les définitions et tout le tralala (on a déjà fait ça dans un autre article, 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 stack 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.\nAvec 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.\nToutefois, 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.\nFocalisez-vous plutôt sur comment cela pour être bénéfique pour leur activité.\nEn général, cela se résume à des revenus ou à une meilleure expérience pour eux, leur développeurs et leur audience.\nDe meilleures performances\n\nPlus 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. — ThinkWithGoogle\n\nLa 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.\nC'est là que se trouve le premier grand argument de vente de la Jamstack : de meilleures performances. 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.\nD'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.\nSécurité accrue et mise à l'échelle plus facile\nPersonne 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 exploits 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.\nPour 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.\nHébergement\nNous 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.\nExpliquer la Jamstack au client\nLorsqu'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.\nAlors, comment communiquez-vous tous les avantages avec votre client ?\nPosez des questions et informez vos clients\nCommencez 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.\nEnsuite, mettez votre client à niveau pour qu'il puisse lui aussi comprendre de quoi vous parlez. Comment ? Aidez votre client à comprendre la Jamstack dans une langue qu'il comprendra beaucoup plus facilement (rappelez-vous que c'est pour cela que vous avez posé beaucoup de questions).\nFaites 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.\nFaîtes des études de cas\nLes é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.\nSi 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.\nDonnez-leur des options, laissez-les choisir\nSi 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.\nEn résumé…\nLa 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.\nC'est votre travail de vous assurer qu'ils obtiennent ce dont ils ont besoin et de comprendre la logique et la technologie sous-jacente.",
      "content_html": "<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>\n<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>\n<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>\n<p>Mais ça ne marchera pas avec vos clients.</p>\n<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>\n<h2 id=\"les-benefices-de-la-jamstack\">Les bénéfices de la Jamstack</h2>\n<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>\n<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>\n<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>\n<p>Focalisez-vous plutôt sur comment cela pour être bénéfique pour leur activité.\nEn général, cela se résume à des revenus ou à une meilleure expérience pour eux, leur développeurs et leur audience.</p>\n<h3 id=\"de-meilleures-performances\">De meilleures performances</h3>\n<blockquote>\n<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>\n</blockquote>\n<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>\n<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>\n<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>\n<h3 id=\"securite-accrue-et-mise-a-l-echelle-plus-facile\">Sécurité accrue et mise à l'échelle plus facile</h3>\n<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>\n<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>\n<h3 id=\"hebergement\">Hébergement</h3>\n<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>\n<h2 id=\"expliquer-la-jamstack-au-client\">Expliquer la Jamstack au client</h2>\n<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>\n<p>Alors, comment communiquez-vous tous les avantages avec votre client ?</p>\n<h3 id=\"posez-des-questions-et-informez-vos-clients\">Posez des questions et informez vos clients</h3>\n<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>\n<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>\n<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>\n<h3 id=\"faites-des-etudes-de-cas\">Faîtes des études de cas</h3>\n<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>\n<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>\n<h3 id=\"donnez-leur-des-options-laissez-les-choisir\">Donnez-leur des options, laissez-les choisir</h3>\n<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>\n<h2 id=\"en-resume\">En résumé…</h2>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/12/03/mise-en-cache-fichiers-partiels-hugo/",
      "url": "https://jamstatic.fr/2019/12/03/mise-en-cache-fichiers-partiels-hugo/",
      "title": "Gestion du cache des fichiers partiels avec Hugo",
      "summary": "Apprenez à optimiser le cache des fichiers partiels pour reduire plus encore vos temps de génération avec Hugo.",
      "date_published": "2019-12-03T17:10:24+00:00",
      "date_modified": "2019-12-07T17:13:54+00:00","content_text": "\n\n\n\n\n\nIllustration des partiels d'Hugo\n\nLes 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.\nNous n'allons pas revenir dans cet article sur les bases des partiels comme leur contexte que nous avons déjà détaillé auparavant.\nNous 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é.\nEntrons dans le vif du sujet sans plus attendre avec la solution dédiée à la mise en cache des fichiers partiels : partialCached\nPourquoi utiliser partialCached ?\nComme 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.\nSi comme moi vous avez horreur de copier-coller des bouts de code un peu partout dans vos projets, vous devez avoir beaucoup recours aux fichiers partiels !\nLe 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.\nSongez à l'entête de page de votre site par exemple.\nVotre 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.\nSi 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.\nAvec partialCached 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;:\n{{ partialCached \"header.html\" . }}\nC'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, vous venez de potentiellement gagner pas mal de précieuses millisecondes ⏱️!\nMais notre entête de page est-il vraiment identique sur toutes les pages ?\nNon, 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.\nLe code de notre menu Hugo contient souvent quelque chose comme :\n&lt;nav&gt;\n{{ range .Site.Menus.main }}\n  &lt;a\n    class=\"{{ if eq $currentPage.Section .Page.Section }} active {{ end }}\"\n    href=\"{{ .URL }}\"\n  &gt;\n    {{ .Name }}\n  &lt;\/a&gt;\n{{ end }}\n&lt;\/nav&gt;\nLe 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 Blog du menu lorsqu'on se trouve sur une page de la section Blog, nous comparons la section de la page visitée ($currentPage.Section) avec celle de l'entrée de menu (.Page.Section).\nAvec le {{ partialCached \"header.html\" . }} actuel, Hugo va maintenant évaluer une seule fois la condition de ce if et appliquer son résultat à toutes les pages suivantes générées, et ce quelle que soit leur section.\nHeureusement il y a les variantes de partiel.\nLes variantes de partiel\nNous savons que notre entête va seulement être modifié cinq fois, en fonction de la .Section de la page courante. Nous devons donc dire à Hugo de mettre en cache une différente variante du partiel en fonction de ce facteur.\nContrairement à la fonction partial, les arguments de partialCached ne se limitent pas au contexte.\nPour nos cas d'utilisation, il est clair que la variante est la .Section de la page courante, nous pouvons donc écrire ceci :\n{{ partialCached \"navigation.html\" . .Section }}\n🎉 C'est 995 fois où Hugo n'aura pas à interpréter le code de ce partiel.\nBien. Cela fait une variante en moins, mais quid si quelque chose d'autre doit changer et que ce n'est pas lié à la section ?\nPar exemple sur mon site, 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.\nLe code ressemble à ça :\n{{ if ne .Layout \"contact\" }}\n  {{ range site.Socials }}\n    {{\/* vous voyez l'idée *\/}}\n  {{ end }}\n{{ end }}\nNous avons donc maintenant besoin de deux variantes, la variante .Section et la variante Est-ce la page de contact ?.\nHeureusement pour nous, le nombre de variantes n'est pas limité, alors allons-y gaiement :\n{{ partialCached \"navigation.html\" . .Section \"contact\" }}\nOK, c'était simplement à des fins de clarté et de lisibilité mais soyons réaliste, vous aurez vraisemblablement besoin de quelque chose de plus \"dynamique\" :\n{{ $layout := cond (eq .Layout \"contact\") \"contact\" \"other\" }}\n{{ partialCached \"navigation.html\" . .Section $layout }}\n🎉 C'est maintenant 994 fois où Hugo n'aura pas à interpréter le code de ce partiel.\nHaussons le niveau d'un cran 💪\nPlongeons-nous maintenant dans quelque chose d'un peu plus complexe.\nNotre 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.\nIci, 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 :\n{{ partialCached \"authors-box.html\" . .Params.authors }}\nMalheureusement à l'heure actuelle, les variantes passées en argument de la fonction partialCached doivent être des chaînes de caractères 🤷.\nPour 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 printf avec le bon verbe.\nPersonnellement j'aime bien %x, car il va générer la représentation d'une valeur en chaîne hexadécimale, quelque que soit le type de structure.\nAdmettons que nous ayons :\nauthors:\n  - Bud Parr\n  - Frank Taillandier\n  - Régis Philibert\n{{ $variant := printf \"%x\" .Params.authors }}\n🖨️👇\n[42756420506172724672616e6b205461696c6c616e6469657252c3a9676973205068696c6962657274]\nNous avons maintenant une chaîne de caractères que nous pouvons passer comme variante du partiel :\n{{ with .Params.authors }}\n  {{ $authors := sort . }}\n  {{ $variant := printf \"%x\" $authors }}\n  {{ partialCached \"authors-box.html\" . $variant }}\n{{ end }}\nGrâ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.\nPourquoi ordonner les auteurs ?\nEn 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.\nCette 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.\nEt les langues ? 🇫🇷🇬🇧\nDans 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 :\n{{ partialCached \"navigation.html\" . .Section .Lang }}\nMais 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.\nDans notre cas de figure, Hugo va donc calculer le balisage de notre entête dix fois.\nLa règle d'or pour connaître le \"calcul\" du nombre de partiels est :\npartiel x variantes x langues\nAméliorer votre temps de génération ⏱️\nPour les sites que vous avez développé vous-même, il est relativement facile d'aller inspecter votre dossier partials 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: hugo --templateMetrics --templateMetricsHints.\nLa 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.\nCes options vous aideront à identifier les principaux goulots d'étranglement, cependant vous devriez tout le temps garder ces trois points en tête:\n\nLe niveau de complexité de votre fichier partiel et sa durée de génération ajoutée au temps total.\nLa comparaison entre le nombre de fois où un partiel sera traité et son potentiel de variantes.\nL'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)\n\nConclusion\nLorsque 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!\nAllez 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 partialCached !",
      "content_html": "<figure>\n<picture title=\"Illustration des partiels d&#039;Hugo\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Illustration des partiels d'Hugo</figcaption>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"pourquoi-utiliser-partialcached\">Pourquoi utiliser partialCached ?</h2>\n<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>\n<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>\n<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>\n<p>Songez à l'entête de page de votre site par exemple.</p>\n<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>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ partialCached <span class=\"hljs-string\">\"header.html\"</span> . }}</code></pre>\n<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>\n<p>Mais notre entête de page est-il vraiment identique sur toutes les pages ?</p>\n<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>\n<p>Le code de notre menu Hugo contient souvent quelque chose comme :</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;nav&gt;\n{{ <span class=\"hljs-keyword\">range</span> .Site.Menus.main }}\n  &lt;a\n    class=<span class=\"hljs-string\">\"{{ if eq $currentPage.Section .Page.Section }} active {{ end }}\"</span>\n    href=<span class=\"hljs-string\">\"{{ .URL }}\"</span>\n  &gt;\n    {{ .Name }}\n  &lt;/a&gt;\n{{ end }}\n&lt;/nav&gt;</code></pre>\n<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>\n<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>\n<p>Heureusement il y a les variantes de partiel.</p>\n<h2 id=\"les-variantes-de-partiel\">Les variantes de partiel</h2>\n<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>\n<p>Contrairement à la fonction <code>partial</code>, les arguments de <code>partialCached</code> ne se limitent pas au contexte.</p>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ partialCached <span class=\"hljs-string\">\"navigation.html\"</span> . .Section }}</code></pre>\n<p>🎉 C'est 995 fois où Hugo n'aura pas à interpréter le code de ce partiel.</p>\n<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>\n<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>\n<p>Le code ressemble à ça :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">if</span> ne .Layout <span class=\"hljs-string\">\"contact\"</span> }}\n  {{ <span class=\"hljs-keyword\">range</span> site.Socials }}\n    {{<span class=\"hljs-comment\">/* vous voyez l'idée */</span>}}\n  {{ end }}\n{{ end }}</code></pre>\n<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>\n<p>Heureusement pour nous, le nombre de variantes n'est pas limité, alors allons-y gaiement :</p>\n<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>\n<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>\n<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> }}\n{{ partialCached <span class=\"hljs-string\">\"navigation.html\"</span> . .Section $layout }}</code></pre>\n<p>🎉 C'est maintenant 994 fois où Hugo n'aura pas à interpréter le code de ce partiel.</p>\n<h2 id=\"haussons-le-niveau-d-un-cran-rџ-Ye\">Haussons le niveau d'un cran 💪</h2>\n<p>Plongeons-nous maintenant dans quelque chose d'un peu plus complexe.</p>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ partialCached <span class=\"hljs-string\">\"authors-box.html\"</span> . .Params.authors }}</code></pre>\n<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>\n<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>\n<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>\n<p>Admettons que nous ayons :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">authors:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">Bud</span> <span class=\"hljs-string\">Parr</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">Frank</span> <span class=\"hljs-string\">Taillandier</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">Régis</span> <span class=\"hljs-string\">Philibert</span></code></pre>\n<pre><code class=\"language-go-html-template hljs go\">{{ $variant := printf <span class=\"hljs-string\">\"%x\"</span> .Params.authors }}</code></pre>\n<p>🖨️👇\n<code>[42756420506172724672616e6b205461696c6c616e6469657252c3a9676973205068696c6962657274]</code></p>\n<p>Nous avons maintenant une chaîne de caractères que nous pouvons passer comme variante du partiel :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ with .Params.authors }}\n  {{ $authors := sort . }}\n  {{ $variant := printf <span class=\"hljs-string\">\"%x\"</span> $authors }}\n  {{ partialCached <span class=\"hljs-string\">\"authors-box.html\"</span> . $variant }}\n{{ end }}</code></pre>\n<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>\n<aside class=\"note\"><h3 id=\"pourquoi-ordonner-les-auteurs\">Pourquoi ordonner les auteurs ?</h3>\n<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>\n<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>\n<h2 id=\"et-les-langues-rџ-rџ-rџ-rџ\">Et les langues ? 🇫🇷🇬🇧</h2>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ partialCached <span class=\"hljs-string\">\"navigation.html\"</span> . .Section .Lang }}</code></pre>\n<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.\nDans notre cas de figure, Hugo va donc calculer le balisage de notre entête dix fois.</p>\n<aside class=\"note note-🧮\"><p>La règle d'or pour connaître le \"calcul\" du nombre de partiels est :<br>\n<code>partiel x variantes x langues</code></p></aside>\n<h2 id=\"amG-c-liorer-votre-temps-de-gG-c-nG-c-ration-vЏ-pyoЏ\">Améliorer votre temps de génération ⏱️</h2>\n<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>\n<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>\n<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>\n<ol>\n<li>Le niveau de complexité de votre fichier partiel et sa durée de génération ajoutée au temps total.</li>\n<li>La comparaison entre le nombre de fois où un partiel sera traité et son potentiel de variantes.</li>\n<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>\n</ol>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>\n<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>",
      "authors": [
        {
          "name": "regis"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/10/30/git-based-cms-vs-api-first-cms/",
      "url": "https://jamstatic.fr/2019/10/30/git-based-cms-vs-api-first-cms/",
      "title": "Git-based, API-first headless CMS : lequel choisir ?",
      "summary": "Les différences à connaître entre les headless CMS qui reposent sur Git et ceux qui fournissent une API.",
      "date_published": "2019-10-30T09:26:33+00:00","content_text": "L'agence Bejamas 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.\nDans 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'API d'une plate-forme Git de vous permettre d'accéder à votre tour à ces contenus via une API de votre cru. Générer des fichiers JSON, les générateurs actuels comme Hugo ou Eleventy font ça très bien. 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 DIY, qui peut soit dit en passant tout à fait être réutilisable de projet en projet.\nIl vous faut aussi prendre en compte dans l'équation votre workflow éditorial, la taille de votre site (et donc le temps nécessaire à la génération d'une nouvelle version) ainsi que votre fréquence de publication. Autant de paramètres qui doivent peser dans le choix d'un headless 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.\nAlors 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.\nWordPress par exemple (qui date du début des années 2000) est utilisé à l'heure actuelle par 34% des sites web. 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.\nMais il faut bien que le contenu de votre site web vive quelque part, n'est-ce pas ?\nBienvenue dans le monde des CMS headless. Plutôt que de lister quelques uns des headless CMS 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. 1\nLes CMS basés sur Git\n\n\n\n\n\nAvec 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.\nPour\n\nPas de verrou propriétaire sur vos contenus.\nGestion de version de tous les contenus par défaut.\nTous les contenus étant disponibles sous forme de fichiers texte, les développeurs peuvent continuer d'utiliser leurs outils habituels.\nFacilite l'annulation de changements.\nC'est l'approche la plus homogène pour la plupart des développeurs web, qui utilisent déjà un workflow basé sur Git.\nFacile à configurer.\n\nContre\n\nSi 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.\nSi 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.\nSi 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.\nMoins d'options de modélisation de contenu et de formatage.\n\nLes CMS API-first\n\n\n\n\n\nAvec 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.\nPour\n\nC'est la meilleure solution si vous maintenez plusieurs applications et sites web qui récupèrent le même contenu structuré.\nFacile à utiliser avec de multiples terminaux client.\nBeaucoup d'options disponibles pour vous permettre de personnaliser le CMS.\nVous avez des quantités très importantes de données à gérer.\n\nContre\n\nPas de gestion de version dans Git, ni d'intégration dans le workflow de développement.\nVa 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.\nDépendance aux développeurs pour des gros changements.\n\nMaintenant 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.\nLes bénéfices des CMS basés sur Git\nLa gestion de version multi-objets\nLes plates-formes de CMS traditionnelles comme Drupal, Kirby, ou Wordpress 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.\nCette 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.\nBien 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.\nLa 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é.\nC'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.\nDépôt distribué\nContrairement 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.\nLa gestion de version distribuée et le flux de développement sont simples et rapides.\nAvec 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.\nRamifications\nGrâ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.\nLes 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.\nNdT: la conférence de Shawn Erquhart explique en détail pourquoi les CMS basés sur Git sont des outils plus puissants qu'il n'y paraît.\n\n\n\nLes bénéfices des CMS basés sur des APIs\nAtteindre les consommateurs sur des terminaux variés\nBeaucoup de personnes se tournent aujourd'hui vers des appareils à commande vocale comme Amazon Echo et Google Home pour écouter les actualités, faire leurs achats en ligne, définir des rappels, rechercher sur le web, etc.\nLes 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 API pour ces terminaux pour les faire communiquer avec leur système.\nVous pouvez alors fournir différentes expériences sur absolument n'importe quel périphérique sans restriction aucune.\nMeilleure intégration pour le marketing\nDans 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.\nRediffusion du contenu plus rapide\nAvec 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 omnicanal 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.\nLes défis posés par les CMS basés sur Git\nAdopter des mesures spécifiques pour le SEO\nFaute 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.\nLes limitations de GitHub Pages\nGitHub 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 2.\nLes défis posés par les CMS basés sur des APIs\nUne grande dépendance aux développeurs web\nCré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. 3\n(Pour certains) la prévisualisation de contenu est problématique\nLes 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.\nLes coûts\nVous 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.\nAlors quel CMS headless devez vous choisir ?\nCe 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.\nBien 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.\nListe de plate-formes CMS basées sur Git\n\nForestry - https:\/\/forestry.io\nNetlify CMS - https:\/\/www.netlifycms.org\nTinaCMS - https:\/\/tinacms.org\nPublii - https:\/\/getpublii.com\nProse - http:\/\/prose.io\nCrafter CMS - https:\/\/craftercms.org\n\nListe de plates-formes CMS basées sur des APis\n\nSanity - https:\/\/sanity.io\nDato CMS - https:\/\/www.datocms.com\/\nStrapi - https:\/\/strapi.io\nStoryblok - https:\/\/www.storyblok.com\/\nPrismic - https:\/\/prismic.io\nContentful - https:\/\/www.contentful.com\nGhost - https:\/\/ghost.org\nCloud CMS - https:\/\/www.cloudcms.com\nDirectus - https:\/\/directus.io\nRooftop - https:\/\/www.rooftopcms.com\n\n\n\n\n\nNdT: 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 manuellement une API à consommer depuis différents terminaux clients.&#160;&#8617;\n\n\nIl existe d'autres solutions dédiées pour le déploiement de sites statiques comme Netlify ou Vercel pour n'en citer que deux.&#160;&#8617;\n\n\nC'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;&#8617;\n\n\n",
      "content_html": "<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.\nDans 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.\nIl 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>\n<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>\n<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>\n<p>Mais il faut bien que le contenu de votre site web vive quelque part, n'est-ce pas ?</p>\n<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>\n<h2 id=\"les-cms-bases-sur-git\">Les CMS basés sur Git</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<h3 id=\"潰牵\">Pour</h3>\n<ul>\n<li>Pas de verrou propriétaire sur vos contenus.</li>\n<li>Gestion de version de <em>tous</em> les contenus par défaut.</li>\n<li>Tous les contenus étant disponibles sous forme de fichiers texte, les développeurs peuvent continuer d'utiliser leurs outils habituels.</li>\n<li>Facilite l'annulation de changements.</li>\n<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>\n<li>Facile à configurer.</li>\n</ul>\n<h3 id=\"contre\">Contre</h3>\n<ul>\n<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>\n<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>\n<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>\n<li>Moins d'options de modélisation de contenu et de formatage.</li>\n</ul>\n<h2 id=\"les-cms-api-first\">Les CMS API-first</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<h3 id=\"潰牵-1\">Pour</h3>\n<ul>\n<li>C'est la meilleure solution si vous maintenez plusieurs applications et sites web qui récupèrent le même contenu structuré.</li>\n<li>Facile à utiliser avec de multiples terminaux client.</li>\n<li>Beaucoup d'options disponibles pour vous permettre de personnaliser le CMS.</li>\n<li>Vous avez des quantités très importantes de données à gérer.</li>\n</ul>\n<h3 id=\"contre-1\">Contre</h3>\n<ul>\n<li>Pas de gestion de version dans Git, ni d'intégration dans le workflow de développement.</li>\n<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>\n<li>Dépendance aux développeurs pour des gros changements.</li>\n</ul>\n<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>\n<h2 id=\"les-benefices-des-cms-bases-sur-git\">Les bénéfices des CMS basés sur Git</h2>\n<h3 id=\"la-gestion-de-version-multi-objets\">La gestion de version multi-objets</h3>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h3 id=\"depot-distribue\">Dépôt distribué</h3>\n<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>\n<p>La gestion de version distribuée et le flux de développement sont simples et rapides.</p>\n<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>\n<h3 id=\"ramifications\">Ramifications</h3>\n<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>\n<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>\n<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>\n<p><div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div></p>\n<h2 id=\"les-benefices-des-cms-bases-sur-des-apis\">Les bénéfices des CMS basés sur des APIs</h2>\n<h3 id=\"atteindre-les-consommateurs-sur-des-terminaux-varies\">Atteindre les consommateurs sur des terminaux variés</h3>\n<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>\n<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>\n<p>Vous pouvez alors fournir différentes expériences sur absolument n'importe quel périphérique sans restriction aucune.</p>\n<h3 id=\"meilleure-integration-pour-le-marketing\">Meilleure intégration pour le marketing</h3>\n<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>\n<h3 id=\"rediffusion-du-contenu-plus-rapide\">Rediffusion du contenu plus rapide</h3>\n<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>\n<h2 id=\"les-defis-poses-par-les-cms-bases-sur-git\">Les défis posés par les CMS basés sur Git</h2>\n<h3 id=\"adopter-des-mesures-specifiques-pour-le-seo\">Adopter des mesures spécifiques pour le SEO</h3>\n<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>\n<h3 id=\"les-limitations-de-github-pages\">Les limitations de GitHub Pages</h3>\n<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>\n<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>\n<h3 id=\"une-grande-dependance-aux-developpeurs-web\">Une grande dépendance aux développeurs web</h3>\n<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>\n<h3 id=\"pour-certains-la-previsualisation-de-contenu-est-problematique\">(Pour certains) la prévisualisation de contenu est problématique</h3>\n<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>\n<h3 id=\"les-couts\">Les coûts</h3>\n<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>\n<h2 id=\"alors-quel-cms-headless-devez-vous-choisir\">Alors quel CMS headless devez vous choisir ?</h2>\n<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>\n<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>\n<h2 id=\"liste-de-plate-formes-cms-basees-sur-git\">Liste de plate-formes CMS basées sur Git</h2>\n<ul>\n<li>Forestry - <a href=\"https://forestry.io\" target=\"_blank\" rel=\"noopener noreferrer\">https://forestry.io</a></li>\n<li>Netlify CMS - <a href=\"https://www.netlifycms.org\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.netlifycms.org</a></li>\n<li>TinaCMS - <a href=\"https://tinacms.org\" target=\"_blank\" rel=\"noopener noreferrer\">https://tinacms.org</a></li>\n<li>Publii - <a href=\"https://getpublii.com\" target=\"_blank\" rel=\"noopener noreferrer\">https://getpublii.com</a></li>\n<li>Prose - <a href=\"http://prose.io\" target=\"_blank\" rel=\"noopener noreferrer\">http://prose.io</a></li>\n<li>Crafter CMS - <a href=\"https://craftercms.org\" target=\"_blank\" rel=\"noopener noreferrer\">https://craftercms.org</a></li>\n</ul>\n<h2 id=\"liste-de-plates-formes-cms-basees-sur-des-apis\">Liste de plates-formes CMS basées sur des APis</h2>\n<ul>\n<li>Sanity - <a href=\"https://sanity.io\" target=\"_blank\" rel=\"noopener noreferrer\">https://sanity.io</a></li>\n<li>Dato CMS - <a href=\"https://www.datocms.com/\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.datocms.com/</a></li>\n<li>Strapi - <a href=\"https://strapi.io\" target=\"_blank\" rel=\"noopener noreferrer\">https://strapi.io</a></li>\n<li>Storyblok - <a href=\"https://www.storyblok.com/\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.storyblok.com/</a></li>\n<li>Prismic - <a href=\"https://prismic.io\" target=\"_blank\" rel=\"noopener noreferrer\">https://prismic.io</a></li>\n<li>Contentful - <a href=\"https://www.contentful.com\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.contentful.com</a></li>\n<li>Ghost - <a href=\"https://ghost.org\" target=\"_blank\" rel=\"noopener noreferrer\">https://ghost.org</a></li>\n<li>Cloud CMS - <a href=\"https://www.cloudcms.com\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.cloudcms.com</a></li>\n<li>Directus - <a href=\"https://directus.io\" target=\"_blank\" rel=\"noopener noreferrer\">https://directus.io</a></li>\n<li>Rooftop - <a href=\"https://www.rooftopcms.com\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.rooftopcms.com</a></li>\n</ul>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:git\">\n<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>\n</li>\n<li id=\"fn:deploiement\">\n<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>\n</li>\n<li id=\"fn:developpeurs\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/09/28/navigation-multilingue-eleventy/",
      "url": "https://jamstatic.fr/2019/09/28/navigation-multilingue-eleventy/",
      "title": "Navigation multilingue avec Eleventy",
      "summary": "Une approche pour gérer la navigation sur un site Jamstack multilingue généré avec Eleventy.",
      "date_published": "2019-09-28T00:00:00+00:00","content_text": "Mise en place\nDé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.\nIl existe beaucoup d'articles sur le sujet, comme celui que nous avons publié sur la gestion d'un site multilingue avec Eleventy. Hugo dispose d'une documentation très claire sur le sujet, de nombreux articles en font de même pour Jekyll.\nNotre 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.\nIci, nous utilisons Eleventy, mais l'approche serait similaire avec d'autres générateurs de site statique.\nPour parvenir à nos fins, il nous faut :\n\nPouvoir boucler sur les différents langages utilisés sur le site\nNous assurer que chaque contenu dispose d'une clé locale 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 comment faire avec les fichiers de données de répertoire avec Eleventy.\nDéfinir une clé commune unique nommée translationKey pour relier les traductions d'un contenu entre elles.\n\nLes langues du site\nNous allons commencer par créer un tableau des langues utilisées par notre site. J'ai pour habitude de définir un objet languages dans le fichier de données .\/src\/_data\/site.js.\nmodule.exports = {\n  title: \"Webstoemp\",\n  description:\n    \"Webstoemp is the portfolio and blog of Jérôme Coupé, a designer and front-end developer from Brussels, Belgium.\",\n  url: \"https:\/\/www.webstoemp.com\",\n  baseUrl: \"\/\",\n  author: \"Jerôme Coupé\",\n  authorTwitter: \"@jeromecoupe\",\n  buildTime: new Date(),\n  languages: [\n    {\n      label: \"english\",\n      code: \"en\",\n    },\n    {\n      label: \"français\",\n      code: \"fr\",\n    },\n  ],\n};\nÀ l'aide de ce tableau nous allons pouvoir parcourir les différentes langues de notre site et comparer la valeur du code de langue avec celui de la locale définie dans nos fichiers de contenu.\nAjout des clés de traduction\nMaintenant 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.\nPar exemple, pour connecter entre elles nos pages de contact dans différentes langues, nous pouvons utiliser :\n.\/src\/en\/pages\/about.njk\n---\npermalink: \"\/{{ locale }}\/about\/index.html\"\ntranslationKey: \"about-page\"\n---\n.\/src\/fr\/pages\/about.njk\n---\npermalink: \"\/{{ locale }}\/a-propos\/index.html\"\ntranslationKey: \"about-page\"\n---\nNous pouvons appliquer le même principe à nos documents de collections, par exemple pour les articles de blog :\n.\/src\/en\/bogposts\/2019-09-12-my-awesome-blogpost.njk\n---\ntitle: \"My awesome blogpost\"\ntranslationKey: \"awesome-blogpost\"\n---\n.\/src\/fr\/bogposts\/2019-09-12-mon-magnifique-article-de-blog.njk\n---\ntitle: \"Mon magnifique article de blog\"\ntranslationKey: \"awesome-blogpost\"\n---\nNous disposons maintenant d'une relation explicite entre les traductions de nos contenus en différentes langues.\nCoder notre sélecteur de langue\nVoici le détail de ce que nous allons faire avec ce petit morceau de code:\n\nBoucler sur toutes les langues déclarées de notre site\nDéfinir la page d'accueil comme valeur par défaut de translatedUrl, valeur qui sera remplacée lorsqu'une traduction de contenu est trouvée.\nDans cette boucle, parcourir tous les contenues du site. Eleventy nous fourni une méthode très pratique avec collections.all.\nPour chaque contenu parcouru, vérifier si la clé translationKey correspond et si sa locale correspond au code de langue en cours. Si une correspondance est trouvée, alors définir translatedUrl avec l'URL de ce contenu.\nUtiliser les valeurs de translatedUrl pour créer les liens de notre sélecteur de langue.\n\n{# Boucler sur les langues du site #}\n{% for lgg in site.languages %}\n  {% if loop.first %}&lt;ul class=\"c-lggnav\"&gt;{% endif %}\n\n  {# Définir translatedUrl à la page d'accueil de cette langue par défaut #}\n  {% set translatedUrl = \"\/\" + lgg.code + \"\/\" %}\n\n  {# Définir la classe de la langue active #}\n  {% set activeClass = \"is-active\" if lgg.code == locale else \"\" %}\n\n  {# Parcourir tous les contenus du site #}\n  {% for item in collections.all %}\n\n    {# Pour chaque contenu, vérifier si\n    - la valeur de sa translationKey correspond à celle du contenu en cours\n    - sa locale correspond au code de langue parcourue #}\n    {% if item.data.translationKey == translationKey and item.data.locale == lgg.code %}\n      {% set translatedUrl = item.url %}\n    {% endif %}\n\n  {% endfor %}\n\n  &lt;li class=\"c-lggnav__item\"&gt;\n    &lt;a class=\"c-lggnav__link  {{ activeClass }}\" href=\"{{ translatedUrl }}\"&gt;{{ lgg.label }}&lt;\/a&gt;\n  &lt;\/li&gt;\n\n  {% if loop.last %}&lt;\/ul&gt;{% endif %}\n{% endfor %}\nEt 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éé collections.all 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.",
      "content_html": "<h2 id=\"mise-en-place\">Mise en place</h2>\n<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>\n<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>\n<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>\n<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>\n<p>Pour parvenir à nos fins, il nous faut :</p>\n<ol>\n<li>Pouvoir boucler sur les différents langages utilisés sur le site</li>\n<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>\n<li>Définir une clé commune unique nommée <code>translationKey</code> pour relier les traductions d'un contenu entre elles.</li>\n</ol>\n<h2 id=\"les-langues-du-site\">Les langues du site</h2>\n<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>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-built_in\">module</span>.exports = {\n  <span class=\"hljs-attr\">title</span>: <span class=\"hljs-string\">\"Webstoemp\"</span>,\n  <span class=\"hljs-attr\">description</span>:\n    <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>,\n  <span class=\"hljs-attr\">url</span>: <span class=\"hljs-string\">\"https://www.webstoemp.com\"</span>,\n  <span class=\"hljs-attr\">baseUrl</span>: <span class=\"hljs-string\">\"/\"</span>,\n  <span class=\"hljs-attr\">author</span>: <span class=\"hljs-string\">\"Jerôme Coupé\"</span>,\n  <span class=\"hljs-attr\">authorTwitter</span>: <span class=\"hljs-string\">\"@jeromecoupe\"</span>,\n  <span class=\"hljs-attr\">buildTime</span>: <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Date</span>(),\n  <span class=\"hljs-attr\">languages</span>: [\n    {\n      <span class=\"hljs-attr\">label</span>: <span class=\"hljs-string\">\"english\"</span>,\n      <span class=\"hljs-attr\">code</span>: <span class=\"hljs-string\">\"en\"</span>,\n    },\n    {\n      <span class=\"hljs-attr\">label</span>: <span class=\"hljs-string\">\"français\"</span>,\n      <span class=\"hljs-attr\">code</span>: <span class=\"hljs-string\">\"fr\"</span>,\n    },\n  ],\n};</code></pre>\n<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>\n<h2 id=\"ajout-des-cles-de-traduction\">Ajout des clés de traduction</h2>\n<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>\n<p>Par exemple, pour connecter entre elles nos pages de contact dans différentes langues, nous pouvons utiliser :</p>\n<p><code>./src/en/pages/about.njk</code></p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\">---\npermalink: \"/</span><span class=\"hljs-template-variable\">{{ locale }}</span><span class=\"xml\">/about/index.html\"\ntranslationKey: \"about-page\"\n---</span></code></pre>\n<p><code>./src/fr/pages/about.njk</code></p>\n<pre><code class=\"language-text\">---\npermalink: \"/{{ locale }}/a-propos/index.html\"\ntranslationKey: \"about-page\"\n---</code></pre>\n<p>Nous pouvons appliquer le même principe à nos documents de collections, par exemple pour les articles de blog :</p>\n<p><code>./src/en/bogposts/2019-09-12-my-awesome-blogpost.njk</code></p>\n<pre><code class=\"language-text\">---\ntitle: \"My awesome blogpost\"\ntranslationKey: \"awesome-blogpost\"\n---</code></pre>\n<p><code>./src/fr/bogposts/2019-09-12-mon-magnifique-article-de-blog.njk</code></p>\n<pre><code class=\"language-text\">---\ntitle: \"Mon magnifique article de blog\"\ntranslationKey: \"awesome-blogpost\"\n---</code></pre>\n<p>Nous disposons maintenant d'une relation explicite entre les traductions de nos contenus en différentes langues.</p>\n<h2 id=\"coder-notre-selecteur-de-langue\">Coder notre sélecteur de langue</h2>\n<p>Voici le détail de ce que nous allons faire avec ce petit morceau de code:</p>\n<ol>\n<li>Boucler sur toutes les langues déclarées de notre site</li>\n<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>\n<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>\n<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>\n<li>Utiliser les valeurs de <code>translatedUrl</code> pour créer les liens de notre sélecteur de langue.</li>\n</ol>\n<pre><code class=\"language-twig hljs twig\"><span class=\"hljs-comment\">{# Boucler sur les langues du site #}</span><span class=\"xml\">\n</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\">\n  </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\">\n\n  </span><span class=\"hljs-comment\">{# Définir translatedUrl à la page d'accueil de cette langue par défaut #}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">set</span></span> translatedUrl = \"/\" + lgg.code + \"/\" %}</span><span class=\"xml\">\n\n  </span><span class=\"hljs-comment\">{# Définir la classe de la langue active #}</span><span class=\"xml\">\n  </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\">\n\n  </span><span class=\"hljs-comment\">{# Parcourir tous les contenus du site #}</span><span class=\"xml\">\n  </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\">\n\n    </span><span class=\"hljs-comment\">{# Pour chaque contenu, vérifier si\n    - la valeur de sa translationKey correspond à celle du contenu en cours\n    - sa locale correspond au code de langue parcourue #}</span><span class=\"xml\">\n    </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\">\n      </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">set</span></span> translatedUrl = item.url %}</span><span class=\"xml\">\n    </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> %}</span><span class=\"xml\">\n\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span><span class=\"xml\">\n\n  <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>\n    <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>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n\n  </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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<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>",
      "authors": [
        {
          "name": "jerome"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/09/08/requeter-api-graphql-eleventy/",
      "url": "https://jamstatic.fr/2019/09/08/requeter-api-graphql-eleventy/",
      "title": "Consommer l'API GraphQL d'un CMS headless avec Eleventy",
      "summary": "Eleventy permet de récupérer les données d'une API GraphQL pour générer des pages statiques, en lieu et place des fichiers Markdown.",
      "date_published": "2019-09-08T21:00:48+00:00",
      "date_modified": "2019-09-08T21:00:48+00:00","content_text": "Les différents types de CMS headless\nSi vous avez besoin d'ajouter un CMS headless à 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.\nDans 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.\nLes CMS headless basés sur Git\nLes CMS basés sur Git comme Netlify CMS ou Forestry 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 :\n\ncode et contenu partagent le même workflow\nle contenu est versionné par Git avec un historique clair\nle contenu stocké sous forme de fichiers texte (markdown, YAML, JSON, etc.) est extrêmement portable\n\nLes APIs de CMS headless\nLes CMS basés sur des APIs comme Contentful ou DatoCMS 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 :\n\nle contenu est destiné à être publié sur différentes plateformes\nvos modèles de données sont hautement relationnels\n\nStructure du projet\nEleventy(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.\nQui 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 ?\nDatoCMS 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.\nSi cet article est écrit pour DatoCMS, cette méthodologie est appliquable à tout CMS headless offrant une API GraphQL.\nVoici l'arborescence de fichiers classique avec laquelle nous allons travailler dans Eleventy :\n+-- src\n  +-- _data\n    +-- blogposts.js\n  +-- _includes\n    +-- layouts\n      +-- base.njk\n  +-- blogposts\n    +-- entry.njk\n    +-- list.njk\n+-- .eleventy.js\n+-- .env\n+-- .env.example\n+-- package-lock.json\n+-- package.json\nConfiguration de DatoCMS\nAprè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é blogposts avec une série de champs et quelques entrées.\nNous pouvons alors utiliser notre token d'API pour nous connecter à l'explorateur de l'API GraphQL afin de visualiser les requêtes et les options disponibles, ainsi que le JSON qui nous est retourné.\nUne fois encore, la plupart des CMS headless avec une API GraphQL offrent cette fonctionnalité d'une manière ou d'une autre.\nConfiguration d'Eleventy\nNous allons devoir nous authentifier au serveur GraphQL de DatoCMS avec notre token d'API. Nous pouvons utiliser dotenv pour le stocker dans un fichier .env que nous ajouterons à notre fichier .gitignore pour éviter qu'il ne soit stocké dans notre dépôt Git. Après avoir installé le paquet, nous créons le fichier .env à la racine du projet et y ajoutons le token de l'API de DatoCMS :\nDATOCMS_TOKEN=\"fak3t0k3n3c52750d04b3d92383b1d\"\nEnsuite, il nous faut ajouter la ligne suivant au début de notre fichier de configuration .eleventy.js :\nrequire(\"dotenv\").config();\nÉtant donné que ce fichier est traité très tôt par Eleventy, notre token sera accessible dans tous nos templates via process.env.DATOCMS_TOKEN.\nUtilisation des fichiers de données JavaScript\nAu lieu d'aller piocher nos données à l'aide de collections et de fichiers Markdown avec du front matter en YAML, nous allons utiliser les fichiers de données JavaScript d'Eleventy. Nous utiliserons le fichier src\/_data\/blogposts.js pour nous connecter à la content delivery API 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 blogposts.\nEleventy sera alors en mesure d'utiliser ce fichier JSON pour générer les pages de détail et d'index de notre blog.\nÇi-dessous, le fichier complet nécessaire pour récupérer tous nos articles de blog, qui se base sur le code de la requête en Vanilla JS donné en exemple dans la documentation de DatoCMS.\nAfin de miniser les dépendances, j'ai privilégié l'utilisation de node-fetch à celle d'Appolo et consorts.\nPar défaut l'API GraphQL de DatoCMS limite à 100 le nombre d'enregistrements retournés par requête (merci à Dan Fascia 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.\n\/\/ paquets requis\nconst fetch = require(\"node-fetch\");\n\n\/\/ token de DatoCMS\nconst token = process.env.DATOCMS_TOKEN;\n\n\/\/ récupération des articles de blog\n\/\/ voir https:\/\/www.datocms.com\/docs\/content-delivery-api\/first-request#vanilla-js-example\nasync function getAllBlogposts() {\n  \/\/ nombre maximal d'enregistrements retournés par requête\n  const recordsPerQuery = 100;\n\n  \/\/ nombre d'enregistrements à ignorer (démarre à 0)\n  let recordsToSkip = 0;\n\n  \/\/ Devons nous faire une nouvelle requête ?\n  let makeNewQuery = true;\n\n  \/\/ Tableau pour stocker les articles de blog\n  let blogposts = [];\n\n  \/\/ Effectuer des requpêtes jusqu'à ce que makeNewQuery passe à faux\n  while (makeNewQuery) {\n    try {\n      \/\/ Initialisation du téléchargement\n      const dato = await fetch(\"https:\/\/graphql.datocms.com\/\", {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application\/json\",\n          Accept: \"application\/json\",\n          Authorization: `Bearer ${token}`\n        },\n        body: JSON.stringify({\n          query: `{\n            allBlogposts(\n              first: ${recordsPerQuery},\n              skip: ${recordsToSkip},\n              orderBy: _createdAt_DESC,\n              filter: {\n                _status: {eq: published}\n              }\n            )\n            {\n              id\n              title\n              slug\n              intro\n              body(markdown: true)\n              _createdAt\n              image {\n                url\n                alt\n              }\n              relatedBlogs {\n                id\n              }\n            }\n          }`\n        })\n      });\n\n      \/\/ Enregistrment de la réponse JSON lorsque la promesse est résolue\n      const response = await dato.json();\n\n      \/\/ Gestion des erreurs DatoCMS\n      if (response.errors) {\n        let errors = response.errors;\n        errors.map((error) =&gt; {\n          console.log(error.message);\n        });\n        throw new Error(\"Aborting: DatoCMS errors\");\n      }\n\n      \/\/ mise à jour du tableau des articles avec les données retournées par la réponse en JSON\n      blogposts = blogposts.concat(response.data.allBlogposts);\n\n      \/\/ itération des valeurs pour la prochaine requête\n      recordsToSkip += recordsPerQuery;\n\n      \/\/ Vérification du nombre d'enregistrements retourné\n      \/\/ S'il y en a moins de 100, on arrête de faire des requêtes\n      if (response.data.allBlogposts.length &lt; recordsPerQuery) {\n        makeNewQuery = false;\n      }\n    } catch (error) {\n      throw new Error(error);\n    }\n  }\n\n  \/\/ mise en forme de l'objet blogposts\n  const blogpostsFormatted = blogposts.map((item) =&gt; {\n    return {\n      id: item.id,\n      date: item._createdAt,\n      title: item.title,\n      slug: item.slug,\n      image: item.image.url,\n      imageAlt: item.image.alt,\n      summary: item.intro,\n      body: item.body,\n      relatedBlogs: item.relatedBlogs\n    };\n  });\n\n  \/\/ retour de l'objet formatté\n  return blogpostsFormatted;\n}\n\n\/\/ export pour 11ty\nmodule.exports = getAllBlogposts;\nPlutô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.\nLes images et les vignettes\nLes fichiers et les images uploadés dans DatoCMS sont stockés sur Imgix, nous pouvons donc ajouter des paramètres à chaque URL d'images 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.\nLa majorité des CMS headless offrent des fonctionnalités similaires, soit en intégrant des services tiers comme Cloudinary ou Uploadcare, soit en proposant leur propre API pour les images.\nChamps relationnels\nL'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 :\n\nJe 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.\nPour les champs relationnels, je récupère seulement l'identifiants des élements relatifs.\nJ'utilise des boucles imbriquées dans les templates pour récupérer les données à l'aide des identifiants.\n\nComme les générateurs de site statique performants comme Hugo ou Eleventy 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.\nGénérer une liste paginée des articles de blog avec 11ty\nGrâce à la fonctionnalité pagination d'Eleventy, nous pouvons parcourir notre fichier JSON (accessible via l'objet blogposts) 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é size.\nVoici le code complet du fichier src\/blogposts\/list.njk :\n---\npagination:\n  data: blogposts\n  size: 12\npermalink: blog{% if pagination.pageNumber &gt; 0 %}\/page{{ pagination.pageNumber + 1}}{% endif %}\/index.html\n---\n\n{% extends \"layouts\/base.njk\" %}\n{% set htmlTitle = item.title %}\n\n{% block content %}\n  &lt;h1&gt;Blogposts&lt;\/h1&gt;\n\n  {# boucler sur les éléments paginés #}\n  {% for item in pagination.items %}\n    {% if loop.first %}&lt;ul&gt;{% endif %}\n      &lt;li&gt;\n        &lt;p&gt;&lt;img src=\"{{ item.image }}?fit=crop&amp;amp;w=200&amp;amp;h=200\" alt=\"{{ item.imageAlt }}\"&gt;&lt;\/p&gt;\n        &lt;h2&gt;&lt;a href=\"\/blog\/{{ item.slug }}\"&gt;{{ item.title }}&lt;\/a&gt;&lt;\/h2&gt;\n        &lt;p&gt;&lt;time datetime=\"{{ item.date | date('Y-M-DD') }}\"&gt;{{ item.date|date(\"MMMM Do, Y\") }}&lt;\/time&gt;&lt;\/p&gt;\n        &lt;p&gt;{{ item.summary }}&lt;\/p&gt;\n      &lt;\/li&gt;\n    {% if loop.last %}&lt;\/ul&gt;{% endif %}\n  {% endfor %}\n\n  {# pagination #}\n  {% if pagination.hrefs | length &gt; 0 %}\n  &lt;ul&gt;\n    {% if pagination.previousPageHref %}\n      &lt;li&gt;&lt;a href=\"{{ pagination.previousPageHref }}\"&gt;Previous page&lt;\/a&gt;&lt;\/li&gt;\n    {% endif %}\n    {% if pagination.nextPageHref %}\n      &lt;li&gt;&lt;a href=\"{{ pagination.nextPageHref }}\"&gt;Next page&lt;\/a&gt;&lt;\/li&gt;\n    {% endif %}\n  &lt;\/ul&gt;\n  {% endif %}\n\n{% endblock %}\nGénérer les pages individuelles pour les articles dans 11ty\nNous 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 src\/blogposts\/entry.njk:\n---\npagination:\n  data: blogposts\n  size: 1\n  alias: blogpost\npermalink: blog\/{{ blogpost.slug }}\/index.html\n---\n{% extends \"layouts\/base.njk\" %}\n{% set htmlTitle = blogpost.title %}\n\n{% block content %}\n  {# blogpost #}\n  &lt;img src=\"{{ blogpost.image }}?fit=crop&amp;amp;w=1024&amp;amp;h=576\"\n       srcset=\"{{ blogpost.image }}?fit=crop&amp;amp;w=600&amp;amp;h=338 600w,\n               {{ blogpost.image }}?fit=crop&amp;amp;w=800&amp;amp;h=450 800w,\n               {{ blogpost.image }}?fit=crop&amp;amp;w=1024&amp;amp;h=576 1024w\"\n       sizes=\"100vw\"\n       class=\"u-fluidimg\"\n       alt=\"{{ blogpost.imageAlt }}\"&gt;\n\n  &lt;h1&gt;{{ blogpost.title }}&lt;\/h1&gt;\n  &lt;p&gt;&lt;time datetime=\"{{ blogpost.date | date('Y-M-DD') }}\"&gt;{{ blogpost.date|date(\"MMMM Do, Y\") }}&lt;\/time&gt;&lt;\/p&gt;\n  &lt;p&gt;{{ blogpost.intro }}&lt;\/p&gt;\n  {{ blogpost.body | safe }}\n\n  {# Articles relatifs #}\n  {% if blogpost.relatedBlogs|length %}\n    &lt;h2&gt;Vous pourriez aussi aimer&lt;\/h2&gt;\n    &lt;ul&gt;\n    {% for item in blogpost.relatedBlogs %}\n      {% for post in blogposts %}\n        {% if post.id == item.id %}\n          &lt;li&gt;\n            &lt;a href=\"\/blog\/{{ post.slug }}\"&gt;{{ post.title }}&lt;\/a&gt;\n          &lt;\/li&gt;\n        {% endif %}\n      {% endfor %}\n    {% endfor %}\n    &lt;\/ul&gt;\n  {% endif %}\n{% endblock %}\nDéclencher automatiquement les builds\nLa 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 créer un hook entrant qui déclenchera la génération de votre site. à chaque reception d'une requête POST.\nDatoCMS 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.\nNous 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.",
      "content_html": "<h2 id=\"les-differents-types-de-cms-headless\">Les différents types de CMS headless</h2>\n<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>\n<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>\n<h3 id=\"les-cms-headless-bases-sur-git\">Les CMS headless basés sur Git</h3>\n<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>\n<ul>\n<li>code et contenu partagent le même workflow</li>\n<li>le contenu est versionné par Git avec un historique clair</li>\n<li>le contenu stocké sous forme de fichiers texte (markdown, YAML, JSON, etc.) est extrêmement portable</li>\n</ul>\n<h3 id=\"les-apis-de-cms-headless\">Les APIs de CMS headless</h3>\n<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>\n<ul>\n<li>le contenu est destiné à être publié sur différentes plateformes</li>\n<li>vos modèles de données sont hautement relationnels</li>\n</ul>\n<h2 id=\"structure-du-projet\">Structure du projet</h2>\n<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>\n<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>\n<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>\n<p>Si cet article est écrit pour DatoCMS, cette méthodologie est appliquable à tout CMS headless offrant une API GraphQL.</p>\n<p>Voici l'arborescence de fichiers classique avec laquelle nous allons travailler dans Eleventy :</p>\n<pre><code class=\"language-text\">+-- src\n  +-- _data\n    +-- blogposts.js\n  +-- _includes\n    +-- layouts\n      +-- base.njk\n  +-- blogposts\n    +-- entry.njk\n    +-- list.njk\n+-- .eleventy.js\n+-- .env\n+-- .env.example\n+-- package-lock.json\n+-- package.json</code></pre>\n<h2 id=\"configuration-de-datocms\">Configuration de DatoCMS</h2>\n<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>\n<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>\n<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>\n<h2 id=\"configuration-d-eleventy\">Configuration d'Eleventy</h2>\n<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>\n<pre><code class=\"language-text\">DATOCMS_TOKEN=\"fak3t0k3n3c52750d04b3d92383b1d\"</code></pre>\n<p>Ensuite, il nous faut ajouter la ligne suivant au début de notre fichier de configuration <code>.eleventy.js</code> :</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\"dotenv\"</span>).config();</code></pre>\n<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>\n<h2 id=\"utilisation-des-fichiers-de-donnees-javascript\">Utilisation des fichiers de données JavaScript</h2>\n<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>\n<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>\n<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>\n<p>Afin de miniser les dépendances, j'ai privilégié l'utilisation de <code>node-fetch</code> à celle d'Appolo et consorts.</p>\n<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>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-comment\">// paquets requis</span>\n<span class=\"hljs-keyword\">const</span> fetch = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\"node-fetch\"</span>);\n\n<span class=\"hljs-comment\">// token de DatoCMS</span>\n<span class=\"hljs-keyword\">const</span> token = process.env.DATOCMS_TOKEN;\n\n<span class=\"hljs-comment\">// récupération des articles de blog</span>\n<span class=\"hljs-comment\">// voir https://www.datocms.com/docs/content-delivery-api/first-request#vanilla-js-example</span>\n<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>{\n  <span class=\"hljs-comment\">// nombre maximal d'enregistrements retournés par requête</span>\n  <span class=\"hljs-keyword\">const</span> recordsPerQuery = <span class=\"hljs-number\">100</span>;\n\n  <span class=\"hljs-comment\">// nombre d'enregistrements à ignorer (démarre à 0)</span>\n  <span class=\"hljs-keyword\">let</span> recordsToSkip = <span class=\"hljs-number\">0</span>;\n\n  <span class=\"hljs-comment\">// Devons nous faire une nouvelle requête ?</span>\n  <span class=\"hljs-keyword\">let</span> makeNewQuery = <span class=\"hljs-literal\">true</span>;\n\n  <span class=\"hljs-comment\">// Tableau pour stocker les articles de blog</span>\n  <span class=\"hljs-keyword\">let</span> blogposts = [];\n\n  <span class=\"hljs-comment\">// Effectuer des requpêtes jusqu'à ce que makeNewQuery passe à faux</span>\n  <span class=\"hljs-keyword\">while</span> (makeNewQuery) {\n    <span class=\"hljs-keyword\">try</span> {\n      <span class=\"hljs-comment\">// Initialisation du téléchargement</span>\n      <span class=\"hljs-keyword\">const</span> dato = <span class=\"hljs-keyword\">await</span> fetch(<span class=\"hljs-string\">\"https://graphql.datocms.com/\"</span>, {\n        <span class=\"hljs-attr\">method</span>: <span class=\"hljs-string\">\"POST\"</span>,\n        <span class=\"hljs-attr\">headers</span>: {\n          <span class=\"hljs-string\">\"Content-Type\"</span>: <span class=\"hljs-string\">\"application/json\"</span>,\n          <span class=\"hljs-attr\">Accept</span>: <span class=\"hljs-string\">\"application/json\"</span>,\n          <span class=\"hljs-attr\">Authorization</span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${token}</span>`</span>\n        },\n        <span class=\"hljs-attr\">body</span>: <span class=\"hljs-built_in\">JSON</span>.stringify({\n          <span class=\"hljs-attr\">query</span>: <span class=\"hljs-string\">`{\n            allBlogposts(\n              first: <span class=\"hljs-subst\">${recordsPerQuery}</span>,\n              skip: <span class=\"hljs-subst\">${recordsToSkip}</span>,\n              orderBy: _createdAt_DESC,\n              filter: {\n                _status: {eq: published}\n              }\n            )\n            {\n              id\n              title\n              slug\n              intro\n              body(markdown: true)\n              _createdAt\n              image {\n                url\n                alt\n              }\n              relatedBlogs {\n                id\n              }\n            }\n          }`</span>\n        })\n      });\n\n      <span class=\"hljs-comment\">// Enregistrment de la réponse JSON lorsque la promesse est résolue</span>\n      <span class=\"hljs-keyword\">const</span> response = <span class=\"hljs-keyword\">await</span> dato.json();\n\n      <span class=\"hljs-comment\">// Gestion des erreurs DatoCMS</span>\n      <span class=\"hljs-keyword\">if</span> (response.errors) {\n        <span class=\"hljs-keyword\">let</span> errors = response.errors;\n        errors.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">error</span>) =&gt;</span> {\n          <span class=\"hljs-built_in\">console</span>.log(error.message);\n        });\n        <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>);\n      }\n\n      <span class=\"hljs-comment\">// mise à jour du tableau des articles avec les données retournées par la réponse en JSON</span>\n      blogposts = blogposts.concat(response.data.allBlogposts);\n\n      <span class=\"hljs-comment\">// itération des valeurs pour la prochaine requête</span>\n      recordsToSkip += recordsPerQuery;\n\n      <span class=\"hljs-comment\">// Vérification du nombre d'enregistrements retourné</span>\n      <span class=\"hljs-comment\">// S'il y en a moins de 100, on arrête de faire des requêtes</span>\n      <span class=\"hljs-keyword\">if</span> (response.data.allBlogposts.length &lt; recordsPerQuery) {\n        makeNewQuery = <span class=\"hljs-literal\">false</span>;\n      }\n    } <span class=\"hljs-keyword\">catch</span> (error) {\n      <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Error</span>(error);\n    }\n  }\n\n  <span class=\"hljs-comment\">// mise en forme de l'objet blogposts</span>\n  <span class=\"hljs-keyword\">const</span> blogpostsFormatted = blogposts.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">item</span>) =&gt;</span> {\n    <span class=\"hljs-keyword\">return</span> {\n      <span class=\"hljs-attr\">id</span>: item.id,\n      <span class=\"hljs-attr\">date</span>: item._createdAt,\n      <span class=\"hljs-attr\">title</span>: item.title,\n      <span class=\"hljs-attr\">slug</span>: item.slug,\n      <span class=\"hljs-attr\">image</span>: item.image.url,\n      <span class=\"hljs-attr\">imageAlt</span>: item.image.alt,\n      <span class=\"hljs-attr\">summary</span>: item.intro,\n      <span class=\"hljs-attr\">body</span>: item.body,\n      <span class=\"hljs-attr\">relatedBlogs</span>: item.relatedBlogs\n    };\n  });\n\n  <span class=\"hljs-comment\">// retour de l'objet formatté</span>\n  <span class=\"hljs-keyword\">return</span> blogpostsFormatted;\n}\n\n<span class=\"hljs-comment\">// export pour 11ty</span>\n<span class=\"hljs-built_in\">module</span>.exports = getAllBlogposts;</code></pre>\n<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>\n<h3 id=\"les-images-et-les-vignettes\">Les images et les vignettes</h3>\n<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>\n<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>\n<h3 id=\"champs-relationnels\">Champs relationnels</h3>\n<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>\n<ul>\n<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>\n<li>Pour les champs relationnels, je récupère seulement l'identifiants des élements relatifs.</li>\n<li>J'utilise des boucles imbriquées dans les templates pour récupérer les données à l'aide des identifiants.</li>\n</ul>\n<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>\n<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>\n<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>\n<p>Voici le code complet du fichier <code>src/blogposts/list.njk</code> :</p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\">---\npagination:\n  data: blogposts\n  size: 12\npermalink: 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\n---\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">extends</span></span> \"layouts/base.njk\" %}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">set</span></span> htmlTitle = item.title %}</span><span class=\"xml\">\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">block</span></span> content %}</span><span class=\"xml\">\n  <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>\n\n  </span><span class=\"hljs-comment\">{# boucler sur les éléments paginés #}</span><span class=\"xml\">\n  </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\">\n    </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\">\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li</span>&gt;</span>\n        <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>\n        <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>\n        <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>\n        <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>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n    </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\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span><span class=\"xml\">\n\n  </span><span class=\"hljs-comment\">{# pagination #}</span><span class=\"xml\">\n  </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\">\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n    </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">if</span></span> pagination.previousPageHref %}</span><span class=\"xml\">\n      <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>\n    </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> %}</span><span class=\"xml\">\n    </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">if</span></span> pagination.nextPageHref %}</span><span class=\"xml\">\n      <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>\n    </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> %}</span><span class=\"xml\">\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span>\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> %}</span><span class=\"xml\">\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endblock</span></span> %}</span></code></pre>\n<h2 id=\"generer-les-pages-individuelles-pour-les-articles-dans-11ty\">Générer les pages individuelles pour les articles dans 11ty</h2>\n<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>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\">---\npagination:\n  data: blogposts\n  size: 1\n  alias: blogpost\npermalink: blog/</span><span class=\"hljs-template-variable\">{{ blogpost.slug }}</span><span class=\"xml\">/index.html\n---\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">extends</span></span> \"layouts/base.njk\" %}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">set</span></span> htmlTitle = blogpost.title %}</span><span class=\"xml\">\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">block</span></span> content %}</span><span class=\"xml\">\n  </span><span class=\"hljs-comment\">{# blogpost #}</span><span class=\"xml\">\n  <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>\n       <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,\n               </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,\n               </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>\n       <span class=\"hljs-attr\">sizes</span>=<span class=\"hljs-string\">\"100vw\"</span>\n       <span class=\"hljs-attr\">class</span>=<span class=\"hljs-string\">\"u-fluidimg\"</span>\n       <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>\n\n  <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>\n  <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>\n  <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>\n  </span><span class=\"hljs-template-variable\">{{ blogpost.body | safe }}</span><span class=\"xml\">\n\n  </span><span class=\"hljs-comment\">{# Articles relatifs #}</span><span class=\"xml\">\n  </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\">\n    <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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n    </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\">\n      </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">for</span></span> post in blogposts %}</span><span class=\"xml\">\n        </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\">\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li</span>&gt;</span>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n        </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> %}</span><span class=\"xml\">\n      </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span><span class=\"xml\">\n    </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span><span class=\"xml\">\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span>\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> %}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endblock</span></span> %}</span></code></pre>\n<h2 id=\"declencher-automatiquement-les-builds\">Déclencher automatiquement les builds</h2>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "jerome"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/09/07/site-multilingue-avec-eleventy/",
      "url": "https://jamstatic.fr/2019/09/07/site-multilingue-avec-eleventy/",
      "title": "Un site multilingue avec Eleventy",
      "summary": "Comment gérer plusieurs langues sur son site Eleventy à l'aide de fichiers de données et de collections.",
      "date_published": "2019-09-07T12:27:07+00:00",
      "date_modified": "2019-09-07T12:27:07+00:00","content_text": "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.\nAfin d'illustrer notre propos, nous allons développer un blog multilingue tout ce qu'il y a de plus classique.\nVoici l'arborescence de fichiers avec laquelle nous allons travailler. C'est une architecture Eleventy standard, dont les principes et les techniques peuvent être appliqués à des projets plus importants.\n+-- src\n  +-- _data\n    +-- site.js\n    +-- footer.js\n    +-- header.js\n  +-- _includes\n    +-- layouts\n      +-- base.njk\n    +-- partials\n      +-- header.njk\n      +-- footer.njk\n  +-- en\n    +-- pages\n      +-- index.html\n      +-- blog.html\n      +-- contact.html\n    +-- posts\n      +-- yyyy-mm-dd-some-blogpost.md\n      +-- posts.json\n    +-- en.json\n  +-- fr\n    +-- pages\n      +-- index.html\n      +-- blog.html\n      +-- contact.html\n    +-- posts\n      +-- yyyy-mm-dd-some-blogpost.md\n      +-- posts.json\n    +-- fr.json\n+-- .eleventy.js\nDéfinition des locales\nLa première consiste à créer nos locales à l'aide des fichiers de données pour les répertoires.\nPour cela il nous suffit d'ajouter les fichiers en.json and fr.json dans nos répertoires de langues. Dans chacun d'entre eux, nous définissons une clé locale. 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.\nPar exemple, notre fichier fr.json contient :\n{\n  \"locale\": \"fr\"\n}\n{{ locale }} va donc maintenant retourner \"fr\" ou \"en\" pour chacun de nos fichiers de layout, en fonction de sa position dans notre arborescence de fichiers.\nFiltre de localisation de date\nNunjucks ne possède pas de filtre pour les dates. Nous pouvons en créer un à l'aide de moment.js et lui passer la valeur de notre locale pour qu'il localise les dates pour nous, ce qui est toujours une partie importante des projets multilingues.\nPour ce faire, nous allons insérer le code suivant dans notre fichier de configueration eleventy.js :\n\/\/ date filter (localized)\neleventyConfig.addNunjucksFilter(\"date\", function (date, format, locale) {\n  locale = locale ? locale : \"en\";\n  moment.locale(locale);\n  return moment(date).format(format);\n});\nÀ présent, nous pouvons utiliser ce filtre dans nos layouts et lui passer le paramètre locale. Notez bien que comme nous avons défini la locale par défaut comme en, nous pouvons omettre de la préciser pour les dates purement numériques. Un petit exemple :\n&lt;p&gt;&lt;time datetime=\"{{ post.date | date('Y-MM-DD') }}\"&gt;{{ post.item|date(\"DD MMMM Y\", locale) }}&lt;\/time&gt;&lt;\/p&gt;\nMaintenant que nos dates sont automatiquement localisées, passons aux collections.\nLocalisation des collections\nNous 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 getFilteredByGlob dans notre fichier eleventy.js.\nmodule.exports = function (eleventyConfig) {\n  eleventyConfig.addCollection(\"posts_en\", function (collection) {\n    return collection.getFilteredByGlob(\".\/src\/en\/posts\/*.md\");\n  });\n};\n\nmodule.exports = function (eleventyConfig) {\n  eleventyConfig.addCollection(\"posts_fr\", function (collection) {\n    return collection.getFilteredByGlob(\".\/src\/fr\/posts\/*.md\");\n  });\n};\nComme nos fichiers de contenu Markdown se trouvent dans des sous-répertoires de nos dossiers de langues, la variable locale est accessible. Nous pouvons par exemple l'utiliser pour créer les permaliens de tous nos articles.\nPlutôt que d'ajouter une variable permalink dans le front matter de chaque fichier, nous pouvons créer un fichier de données posts.json dans chacun de nos dossiers posts avec le contenu suivant:\n{\n  permalink: \"\/{{ locale }}\/blog\/{{ page.fileslug }}\/index.html\";\n}\nNous 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 blog.njk de nos différentes langues.\n{% for post in collections.posts_en | reverse %}\n  {% if loop.first %}&lt;ul&gt;{% endif %}\n    &lt;li&gt;\n\n      &lt;article&gt;\n        &lt;p&gt;&lt;time datetime=\"{{ post.date | date('Y-MM-DD') }}\"&gt;{{ post.date | date(\"DD MMMM[,] Y\", locale) }}&lt;\/time&gt;&lt;\/p&gt;\n        &lt;h3&gt;&lt;a href=\"{{ post.url }}\"&gt;{{ post.data.title }}&lt;\/a&gt;&lt;\/h3&gt;\n      &lt;\/article&gt;\n\n    &lt;\/li&gt;\n\n  {% if loop.last %}&lt;\/ul&gt;{% endif %}\n{% endfor %}\nNotez que nous pourrions aussi aussi utiliser pour nos collections notre variable locale. Il faudrait pour cela utiliser à la place une notation entre crochets pour concaténer les chaînes de caractères.\n{% set posts = collections[\"posts_\" + locale] %}\n{% for post in posts %}\n  {# loop content #}\n{% endfor %}\nLocalisation des layouts et des fichiers partiels\nAlors 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.\nFort heureusement, nous pouvons éviter cela en traduisant simplement certaines chaînes de caractères. Pour ce faire, nous devons créer des fichiers de données génériques 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 locale.\nLayouts\nCommençons par un exemple dans un fichier de layout.\nLe fichier .\/src\/_data\/site.js va rendre accessibles des variables via l'objet site.\nmodule.exports = {\n  buildTime: new Date(),\n  baseUrl: \"https:\/\/www.mysite.com\",\n  name: \"mySite\",\n  twitter: \"@handle\",\n  en: {\n    metaTitle: \"Title in english\",\n    metaDescription: \"Description in english\",\n  },\n  fr: {\n    metaTitle: \"Titre en français\",\n    metaDescription: \"Description en français\",\n  },\n};\nNous pouvons utiliser ces variables dans notre fichier .\/src\/fr\/pages\/index.njk. 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.\n---\npermalink: \/{{ locale }}\/index.html\n---\n\n{% extends \"layouts\/base.njk\" %}\n\n{% set metaTitle = site[locale].metaTitle %}\n{% set metaDescription = site[locale].metaDescription %}\n{% set metaImage = site[locale].metaImage %}\n\n{% block content %}\n  {# page content #}\n{% endblock %}\nPuisque ce layout étend le fichier .\/src\/_includes\/layouts\/base.njk, les variables Nunjucks déclarées dans le gabarit enfant ainsi que les variables globales d'Eleventy sont également accessibles dans ce fichier.\n&lt;!DOCTYPE html&gt;\n&lt;html lang=\"{{ locale }}\"&gt;\n&lt;head&gt;\n  &lt;meta charset=\"utf-8\"&gt;\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;\n  &lt;meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"&gt;\n  &lt;title&gt;{{ metaTitle }}&lt;\/title&gt;\n  &lt;link rel=\"stylesheet\" href=\"\/css\/main.min.css\"&gt;\n\n  &lt;!-- open graph --&gt;\n  &lt;meta property=\"og:type\" content=\"article\"&gt;\n  &lt;meta property=\"og:title\" content=\"{{ metaTitle }}\"&gt;\n  &lt;meta property=\"og:image\" content=\"{{ metaImage }}\"&gt;\n  &lt;meta property=\"og:site_name\" content=\"{{ site.name }}\"&gt;\n  &lt;meta property=\"og:description\" content=\"{{ metaDescription }}\"&gt;\n\n  &lt;!-- twitter --&gt;\n  &lt;meta name=\"twitter:card\" content=\"summary\"&gt;\n  &lt;meta name=\"twitter:site\" content=\"{{ site.twitter }}\"&gt;\n  &lt;meta name=\"twitter:title\" content=\"{{ metaTitle }}\"&gt;\n  &lt;meta name=\"twitter:description\" content=\"{{ metaDescription }}\"&gt;\n  &lt;meta name=\"twitter:image\" content=\"{{ metaImage }}\"&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  {% include \"partials\/siteheader.njk\" %}\n  {% block content %}{% endblock %}\n  {% include \"partials\/sitefooter.njk\" %}\n&lt;\/body&gt;\n&lt;\/html&gt;\nFichiers partiels\nLa traduction de fichiers partiels comme .\/src\/_includes\/partials\/footer.njk est basée sur le même principe.\nPremièrement, nous créons un fichier .\/data\/footer.js et nous utilisons nos locales comme clés.\nmodule.exports = {\n  mapUrl: \"https:\/\/goo.gl\/maps\/3YTkhCgfEgj1PRAd7\",\n  fr: {\n    addressTitle: \"Adresse\",\n    addressStreet: \"Rue du marché\",\n    addressNumber: \"42\",\n    addressPostcode: \"1000\",\n    addressCity: \"Bruxelles\",\n    directionsLabel: \"Itinéraire\",\n  },\n  en: {\n    addressTitle: \"Address\",\n    addressStreet: \"Market street\",\n    addressNumber: \"42\",\n    addressPostcode: \"1000\",\n    addressCity: \"Brussels\",\n    directionsLabel: \"Directions\",\n  },\n};\nEnsuite dans le fichier .\/src\/_includes\/partials\/footer.njk, nous nous basons sur la valeur de notre variable locale pour accéder à ces clés à l'aide de la notation entre crochets.\n&lt;footer&gt;\n  &lt;h2&gt;{{ footer[locale].addressTitle }}&lt;\/h2&gt;\n  &lt;p&gt;\n    {{ footer[locale].addressStreet }}, {{ footer[locale].addressNumber }}&lt;br&gt;\n    {{ footer[locale].addressPostcode }}, {{ footer[locale].addressCity }}\n  &lt;\/p&gt;\n  &lt;p&gt;&lt;a href=\"{{ footer.mapUrl }}\"&gt;{{ footer[locale].directionsLabel }}&lt;\/a&gt;&lt;\/p&gt;\n&lt;\/footer&gt;\nEt voilà, nous avons maintenant un pied de page traduit en plusieurs langues.\nFlexible par nature\nLes 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.",
      "content_html": "<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>\n<p>Afin d'illustrer notre propos, nous allons développer un blog multilingue tout ce qu'il y a de plus classique.</p>\n<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>\n<pre><code class=\"language-text\">+-- src\n  +-- _data\n    +-- site.js\n    +-- footer.js\n    +-- header.js\n  +-- _includes\n    +-- layouts\n      +-- base.njk\n    +-- partials\n      +-- header.njk\n      +-- footer.njk\n  +-- en\n    +-- pages\n      +-- index.html\n      +-- blog.html\n      +-- contact.html\n    +-- posts\n      +-- yyyy-mm-dd-some-blogpost.md\n      +-- posts.json\n    +-- en.json\n  +-- fr\n    +-- pages\n      +-- index.html\n      +-- blog.html\n      +-- contact.html\n    +-- posts\n      +-- yyyy-mm-dd-some-blogpost.md\n      +-- posts.json\n    +-- fr.json\n+-- .eleventy.js</code></pre>\n<h2 id=\"definition-des-locales\">Définition des locales</h2>\n<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>\n<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>\n<p>Par exemple, notre fichier <code>fr.json</code> contient :</p>\n<pre><code class=\"language-js hljs javascript\">{\n  <span class=\"hljs-string\">\"locale\"</span>: <span class=\"hljs-string\">\"fr\"</span>\n}</code></pre>\n<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>\n<h2 id=\"filtre-de-localisation-de-date\">Filtre de localisation de date</h2>\n<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>\n<p>Pour ce faire, nous allons insérer le code suivant dans notre fichier de configueration <code>eleventy.js</code> :</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-comment\">// date filter (localized)</span>\neleventyConfig.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>{\n  locale = locale ? locale : <span class=\"hljs-string\">\"en\"</span>;\n  moment.locale(locale);\n  <span class=\"hljs-keyword\">return</span> moment(date).format(format);\n});</code></pre>\n<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>\n<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>\n<p>Maintenant que nos dates sont automatiquement localisées, passons aux collections.</p>\n<h2 id=\"localisation-des-collections\">Localisation des collections</h2>\n<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>\n<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>{\n  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>{\n    <span class=\"hljs-keyword\">return</span> collection.getFilteredByGlob(<span class=\"hljs-string\">\"./src/en/posts/*.md\"</span>);\n  });\n};\n\n<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>{\n  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>{\n    <span class=\"hljs-keyword\">return</span> collection.getFilteredByGlob(<span class=\"hljs-string\">\"./src/fr/posts/*.md\"</span>);\n  });\n};</code></pre>\n<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>\n<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>\n<pre><code class=\"language-js hljs javascript\">{\n  <span class=\"hljs-attr\">permalink</span>: <span class=\"hljs-string\">\"/{{ locale }}/blog/{{ page.fileslug }}/index.html\"</span>;\n}</code></pre>\n<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>\n<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\">\n  </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\">\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li</span>&gt;</span>\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article</span>&gt;</span>\n        <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>\n        <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>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">article</span>&gt;</span>\n\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n\n  </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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<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>\n<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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">for</span></span> post in posts %}</span><span class=\"xml\">\n  </span><span class=\"hljs-comment\">{# loop content #}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<h2 id=\"localisation-des-layouts-et-des-fichiers-partiels\">Localisation des layouts et des fichiers partiels</h2>\n<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>\n<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>\n<h3 id=\"layouts\">Layouts</h3>\n<p>Commençons par un exemple dans un fichier de layout.</p>\n<p>Le fichier <code>./src/_data/site.js</code> va rendre accessibles des variables via l'objet <code>site</code>.</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-built_in\">module</span>.exports = {\n  <span class=\"hljs-attr\">buildTime</span>: <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Date</span>(),\n  <span class=\"hljs-attr\">baseUrl</span>: <span class=\"hljs-string\">\"https://www.mysite.com\"</span>,\n  <span class=\"hljs-attr\">name</span>: <span class=\"hljs-string\">\"mySite\"</span>,\n  <span class=\"hljs-attr\">twitter</span>: <span class=\"hljs-string\">\"@handle\"</span>,\n  <span class=\"hljs-attr\">en</span>: {\n    <span class=\"hljs-attr\">metaTitle</span>: <span class=\"hljs-string\">\"Title in english\"</span>,\n    <span class=\"hljs-attr\">metaDescription</span>: <span class=\"hljs-string\">\"Description in english\"</span>,\n  },\n  <span class=\"hljs-attr\">fr</span>: {\n    <span class=\"hljs-attr\">metaTitle</span>: <span class=\"hljs-string\">\"Titre en français\"</span>,\n    <span class=\"hljs-attr\">metaDescription</span>: <span class=\"hljs-string\">\"Description en français\"</span>,\n  },\n};</code></pre>\n<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>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\">---\npermalink: /</span><span class=\"hljs-template-variable\">{{ locale }}</span><span class=\"xml\">/index.html\n---\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">extends</span></span> \"layouts/base.njk\" %}</span><span class=\"xml\">\n\n</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\">\n</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\">\n</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\">\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">block</span></span> content %}</span><span class=\"xml\">\n  </span><span class=\"hljs-comment\">{# page content #}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endblock</span></span> %}</span></code></pre>\n<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>\n<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>\n<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>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span>\n  <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>\n  <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>\n  <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>\n  <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>\n  <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>\n\n  <span class=\"hljs-comment\">&lt;!-- open graph --&gt;</span>\n  <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>\n  <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>\n  <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>\n  <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>\n  <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>\n\n  <span class=\"hljs-comment\">&lt;!-- twitter --&gt;</span>\n  <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>\n  <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>\n  <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>\n  <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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span>\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">include</span></span> \"partials/siteheader.njk\" %}</span><span class=\"xml\">\n  </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\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">include</span></span> \"partials/sitefooter.njk\" %}</span><span class=\"xml\">\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></span></code></pre>\n<h3 id=\"fichiers-partiels\">Fichiers partiels</h3>\n<p>La traduction de fichiers partiels comme <code>./src/_includes/partials/footer.njk</code> est basée sur le même principe.</p>\n<p>Premièrement, nous créons un fichier <code>./data/footer.js</code> et nous utilisons nos locales comme clés.</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-built_in\">module</span>.exports = {\n  <span class=\"hljs-attr\">mapUrl</span>: <span class=\"hljs-string\">\"https://goo.gl/maps/3YTkhCgfEgj1PRAd7\"</span>,\n  <span class=\"hljs-attr\">fr</span>: {\n    <span class=\"hljs-attr\">addressTitle</span>: <span class=\"hljs-string\">\"Adresse\"</span>,\n    <span class=\"hljs-attr\">addressStreet</span>: <span class=\"hljs-string\">\"Rue du marché\"</span>,\n    <span class=\"hljs-attr\">addressNumber</span>: <span class=\"hljs-string\">\"42\"</span>,\n    <span class=\"hljs-attr\">addressPostcode</span>: <span class=\"hljs-string\">\"1000\"</span>,\n    <span class=\"hljs-attr\">addressCity</span>: <span class=\"hljs-string\">\"Bruxelles\"</span>,\n    <span class=\"hljs-attr\">directionsLabel</span>: <span class=\"hljs-string\">\"Itinéraire\"</span>,\n  },\n  <span class=\"hljs-attr\">en</span>: {\n    <span class=\"hljs-attr\">addressTitle</span>: <span class=\"hljs-string\">\"Address\"</span>,\n    <span class=\"hljs-attr\">addressStreet</span>: <span class=\"hljs-string\">\"Market street\"</span>,\n    <span class=\"hljs-attr\">addressNumber</span>: <span class=\"hljs-string\">\"42\"</span>,\n    <span class=\"hljs-attr\">addressPostcode</span>: <span class=\"hljs-string\">\"1000\"</span>,\n    <span class=\"hljs-attr\">addressCity</span>: <span class=\"hljs-string\">\"Brussels\"</span>,\n    <span class=\"hljs-attr\">directionsLabel</span>: <span class=\"hljs-string\">\"Directions\"</span>,\n  },\n};</code></pre>\n<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>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">footer</span>&gt;</span>\n  <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>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p</span>&gt;</span>\n    </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>\n    </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\">\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">p</span>&gt;</span>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">footer</span>&gt;</span></span></code></pre>\n<p>Et voilà, nous avons maintenant un pied de page traduit en plusieurs langues.</p>\n<h2 id=\"flexible-par-nature\">Flexible par nature</h2>\n<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>",
      "authors": [
        {
          "name": "jerome"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/09/07/de-jekyll-a-eleventy/",
      "url": "https://jamstatic.fr/2019/09/07/de-jekyll-a-eleventy/",
      "title": "De Jekyll à Eleventy",
      "summary": "Retour d'expérience du passage de Jekyll à Eleventy.",
      "date_published": "2019-09-07T08:59:06+00:00",
      "date_modified": "2019-09-07T10:24:06+00:00","content_text": "Eleventy n'en finit pas de faire des émules, il séduit par sa simplicité et sa flexibilité, Jérôme Coupé a sauté le pas à son tour et il est très satisfait de son choix.\nJekyll 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 mon site, j'ai choisi de partir sur Eleventy.\nChoisir un générateur de site\nLes 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.\nMon 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 à Hugo et Eleventy.\nHugo\nÉ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.\nCependant, les inconvénients d'Hugo sont pour moi la syntaxe de Go HTML template 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.\nEleventy\nCe 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).\nDe plus Eleventy supporte plusieurs langagues de templating.\nConfiguration\nJ'ai opté pour le moteur de templating Nunjucks de Mozilla. Outre cela, je n'avais besoin que de fichiers Markdown et HTML.\nNunjucks n'offre pas de filtres date et limit, je les ai donc ajoutés dans mon fichier de configuration eleventy.js, en utilisant la bibliothèque moment.js pour le filtre de date.\nconst moment = require(\"moment\");\n\n\/\/ limit filter\neleventyConfig.addNunjucksFilter(\"limit\", function (array, limit) {\n  return array.slice(0, limit);\n});\n\n\/\/ date filter\neleventyConfig.addNunjucksFilter(\"date\", function (date, format) {\n  return moment(date).format(format);\n});\nJ'utilise Gulp 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 .eleventyignore situé à la racine de mon projet:\nsrc\/assets\/**\/*\nL'étape suivante a été d'ajouter Eleventy à mon fichier gulpfile.js et d'utiliser la fonction child_process de Node. De cette manière, je peux facilement intégrer Eleventy à mes commandes build et watch.\n\/\/ packages\nconst cp = require(\"child_process\");\n\n\/\/ Eleventy\nfunction build() {\n  return cp.spawn(\"npx\", [\"eleventy\", \"--quiet\"], { stdio: \"inherit\" });\n}\nStructure de données\nCollections\nMon 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.\nEleventy 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.\nPar exemple, j'ai utilisé un fichier blogposts.json dans mon dossier blogposts pour définir le layout et la structure des permaliens pour tous les articles de blog.\n{\n  \"layout\": \"layouts\/blogpost.njk\",\n  \"permalink\": \"blog\/{{ page.fileSlug }}\/index.html\"\n}\nLes projets quant à eux n'ont pas besoin de pages de détail, j'ai donc spécifié la valeur de permalink à false dans le fichier projects.json de mon dossier projects et je n'ai pas défini de layout.\n{\n  \"permalink\": false\n}\nPour créer les deux collections et permettre à Eleventy de générer les fichiers HTML, j'ai utilisé la fonction getFilteredByGlob( glob ) dans mon fichier eleventy.js:\nconst moment = require(\"moment\");\n\nmodule.exports = function (eleventyConfig) {\n  \/\/ blogpost collection\n  eleventyConfig.addCollection(\"blogposts\", function (collection) {\n    return collection.getFilteredByGlob(\".\/src\/blogposts\/*.md\");\n  });\n\n  \/\/ projects collection\n  eleventyConfig.addCollection(\"projects\", function (collection) {\n    return collection.getFilteredByGlob(\".\/src\/projects\/*.md\");\n  });\n\n  \/\/ limit filter\n  eleventyConfig.addNunjucksFilter(\"limit\", function (array, limit) {\n    return array.slice(0, limit);\n  });\n\n  \/\/ date filter\n  eleventyConfig.addNunjucksFilter(\"date\", function (date, format) {\n    return moment(date).format(format);\n  });\n\n  \/\/ Base config\n  return {\n    dir: {\n      input: \"src\",\n      output: \"dist\",\n    },\n  };\n};\nLes fichiers de données\nEleventy 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 _data de votre répertoire de base (src dans mon cas).\nJ'ai donc utilisé un fichier .\/src\/_data\/site.js 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.\nmodule.exports = {\n  title: \"Webstoemp\",\n  description:\n    \"Webstoemp is the portfolio and blog of Jérôme Coupé, a designer and front-end developer from Brussels, Belgium.\",\n  url: \"https:\/\/www.webstoemp.com\",\n  baseUrl: \"\/\",\n  author: \"Jerôme Coupé\",\n  authorTwitter: \"@jeromecoupe\",\n  buildTime: new Date(),\n};\nLa valeur de site.buildTime peut maintenant être utilisée dans le pied de page du site :\n&lt;div class=\"c-sitefooter__copyright\"&gt;\n  &lt;p class=\"u-margin-all-none\"&gt;&amp;copy; Webstoemp {{ site.buildTime | date(\"Y\") }}&lt;\/p&gt;\n&lt;\/div&gt;\nLayouts Nunjucks\nJ'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.\nBlogposts\nBoucler sur la collection blogposts pour afficher les titres et les dates de publication n'est pas très compliqué:\n{% for post in collections.blogposts | reverse %}\n  {% if loop.first %}&lt;ul class=\"c-list-ui\"&gt;{% endif %}\n  &lt;li&gt;\n    &lt;article class=\"c-blogteaser\"&gt;\n      &lt;p class=\"c-blogteaser__date\"&gt;&lt;time datetime=\"{{ post.date | date('Y-M-DD') }}\"&gt;{{ post.date|date(\"MMMM Do, Y\") }}&lt;\/time&gt;&lt;\/p&gt;\n      &lt;h2 class=\"c-blogteaser__title\"&gt;&lt;a href=\"{{ post.url }}\"&gt;{{ post.data.title }}&lt;\/a&gt;&lt;\/h2&gt;\n    &lt;\/article&gt;\n  &lt;\/li&gt;\n  {% if loop.last %}&lt;\/ul&gt;{% endif %}\n{% else %}\n  &lt;p&gt;No blogpost found&lt;\/p&gt;\n{% endfor %}\nJ'utilise un layout dédié pour afficher le détail de chaque article de blog. Le fichier _includes\/layouts\/blogpost.nkj appelle mon layout principal et ajoute l'image associée à l'article de blog, ainsi que le contenu Markdown au bloc content :\n{% extends \"layouts\/base.njk\" %}\n{% set activeSection = \"blog\" %}\n\n{% set metaTitle = title %}\n{% set metaDescription = excerpt %}\n{% set metaImage = site.url ~ \"\/img\/blogposts\/_600x600\/\" ~ image %}\n\n{% block content %}\n\n  &lt;article class=\"c-blogpost\"&gt;\n    &lt;div class=\"c-blogpost__media\"&gt;\n      &lt;div class=\"l-container\"&gt;\n\n        &lt;picture&gt;\n          &lt;source media=\"(min-width: 500px)\"\n                  srcset=\"\/img\/blogposts\/_1024x576\/{{ image }} 1024w,\n                          \/img\/blogposts\/{{ image }} 1500w\"\n                  sizes=\"(min-width: 1140px) 1140px,\n                         100vw\"&gt;\n          &lt;img src=\"\/img\/blogposts\/_600x600\/{{ image }}\"\n               class=\"o-fluidimage\"\n               alt=\"{{ imageAlt }}\"&gt;\n        &lt;\/picture&gt;\n\n      &lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=\"c-blogpost__body  l-container l-container--narrow\"&gt;\n\n      &lt;header&gt;\n        &lt;p class=\"c-suptitle  c-suptitle--dark\"&gt;&lt;time datetime=\"{{ page.date | date('Y-M-DD') }}\"&gt;{{ page.date | date(\"MMMM Do, YYYY\") }}&lt;\/time&gt;&lt;\/p&gt;\n        &lt;h1 class=\"c-h1\"&gt;{{ title }}&lt;\/h1&gt;\n        &lt;div class=\"c-blogpost__intro\"&gt;\n          &lt;p&gt;{{ excerpt }}&lt;\/p&gt;\n        &lt;\/div&gt;\n      &lt;\/header&gt;\n\n      &lt;div class=\"c-wysiwyg\"&gt;\n        {{ content | safe }}\n      &lt;\/div&gt;\n\n    &lt;\/div&gt;\n  &lt;\/article&gt;\n\n{% endblock %}\nProjects\nLa 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 projects, il nous faut utiliser templateContent pour afficher le contenu des fichiers Markdown. Voici une version simplifiée du code :\n{% for project in collections.projects | reverse %}\n    {{ project.templateContent | safe }}\n{% endfor %}\nSatisfait de mon choix\nAu final, je suis très content du résultat et d'avoir opté pour Eleventy. Le code source de mon site est publié sur GitHub, si vous avez envie d'aller y jeter un œil.",
      "content_html": "<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>\n<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>\n<h2 id=\"choisir-un-generateur-de-site\">Choisir un générateur de site</h2>\n<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>\n<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>\n<h3 id=\"hugo\">Hugo</h3>\n<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>\n<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>\n<h3 id=\"eleventy\">Eleventy</h3>\n<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>\n<p>De plus Eleventy supporte <a href=\"https://www.11ty.dev/docs/languages/\" target=\"_blank\" rel=\"noopener noreferrer\">plusieurs langagues de templating</a>.</p>\n<h4 id=\"configuration\">Configuration</h4>\n<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>\n<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>\n<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>);\n\n<span class=\"hljs-comment\">// limit filter</span>\neleventyConfig.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>{\n  <span class=\"hljs-keyword\">return</span> array.slice(<span class=\"hljs-number\">0</span>, limit);\n});\n\n<span class=\"hljs-comment\">// date filter</span>\neleventyConfig.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>{\n  <span class=\"hljs-keyword\">return</span> moment(date).format(format);\n});</code></pre>\n<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>\n<pre><code class=\"language-txt\">src/assets/**/*</code></pre>\n<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>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-comment\">// packages</span>\n<span class=\"hljs-keyword\">const</span> cp = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\"child_process\"</span>);\n\n<span class=\"hljs-comment\">// Eleventy</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">build</span>(<span class=\"hljs-params\"></span>) </span>{\n  <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> });\n}</code></pre>\n<h3 id=\"structure-de-donnees\">Structure de données</h3>\n<h4 id=\"collections\">Collections</h4>\n<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>\n<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>\n<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>\n<pre><code class=\"language-json hljs json\">{\n  <span class=\"hljs-attr\">\"layout\"</span>: <span class=\"hljs-string\">\"layouts/blogpost.njk\"</span>,\n  <span class=\"hljs-attr\">\"permalink\"</span>: <span class=\"hljs-string\">\"blog/{{ page.fileSlug }}/index.html\"</span>\n}</code></pre>\n<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>\n<pre><code class=\"language-json hljs json\">{\n  <span class=\"hljs-attr\">\"permalink\"</span>: <span class=\"hljs-literal\">false</span>\n}</code></pre>\n<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>\n<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>);\n\n<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>{\n  <span class=\"hljs-comment\">// blogpost collection</span>\n  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>{\n    <span class=\"hljs-keyword\">return</span> collection.getFilteredByGlob(<span class=\"hljs-string\">\"./src/blogposts/*.md\"</span>);\n  });\n\n  <span class=\"hljs-comment\">// projects collection</span>\n  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>{\n    <span class=\"hljs-keyword\">return</span> collection.getFilteredByGlob(<span class=\"hljs-string\">\"./src/projects/*.md\"</span>);\n  });\n\n  <span class=\"hljs-comment\">// limit filter</span>\n  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>{\n    <span class=\"hljs-keyword\">return</span> array.slice(<span class=\"hljs-number\">0</span>, limit);\n  });\n\n  <span class=\"hljs-comment\">// date filter</span>\n  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>{\n    <span class=\"hljs-keyword\">return</span> moment(date).format(format);\n  });\n\n  <span class=\"hljs-comment\">// Base config</span>\n  <span class=\"hljs-keyword\">return</span> {\n    <span class=\"hljs-attr\">dir</span>: {\n      <span class=\"hljs-attr\">input</span>: <span class=\"hljs-string\">\"src\"</span>,\n      <span class=\"hljs-attr\">output</span>: <span class=\"hljs-string\">\"dist\"</span>,\n    },\n  };\n};</code></pre>\n<h4 id=\"les-fichiers-de-donnees\">Les fichiers de données</h4>\n<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>\n<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>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-built_in\">module</span>.exports = {\n  <span class=\"hljs-attr\">title</span>: <span class=\"hljs-string\">\"Webstoemp\"</span>,\n  <span class=\"hljs-attr\">description</span>:\n    <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>,\n  <span class=\"hljs-attr\">url</span>: <span class=\"hljs-string\">\"https://www.webstoemp.com\"</span>,\n  <span class=\"hljs-attr\">baseUrl</span>: <span class=\"hljs-string\">\"/\"</span>,\n  <span class=\"hljs-attr\">author</span>: <span class=\"hljs-string\">\"Jerôme Coupé\"</span>,\n  <span class=\"hljs-attr\">authorTwitter</span>: <span class=\"hljs-string\">\"@jeromecoupe\"</span>,\n  <span class=\"hljs-attr\">buildTime</span>: <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Date</span>(),\n};</code></pre>\n<p>La valeur de <code>site.buildTime</code> peut maintenant être utilisée dans le pied de page du site :</p>\n<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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span></span></code></pre>\n<h3 id=\"layouts-nunjucks\">Layouts Nunjucks</h3>\n<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>\n<h4 id=\"blogposts\">Blogposts</h4>\n<p>Boucler sur la collection <code>blogposts</code> pour afficher les titres et les dates de publication n'est pas très compliqué:</p>\n<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\">\n  </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\">\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li</span>&gt;</span>\n    <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>\n      <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>\n      <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>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">article</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n  </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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">else</span> %}</span><span class=\"xml\">\n  <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>\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<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>\n<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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">set</span></span> activeSection = \"blog\" %}</span><span class=\"xml\">\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">set</span></span> metaTitle = title %}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">set</span></span> metaDescription = excerpt %}</span><span class=\"xml\">\n</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\">\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">block</span></span> content %}</span><span class=\"xml\">\n\n  <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>\n    <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>\n      <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>\n\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">picture</span>&gt;</span>\n          <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>\n                  <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,\n                          /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>\n                  <span class=\"hljs-attr\">sizes</span>=<span class=\"hljs-string\">\"(min-width: 1140px) 1140px,\n                         100vw\"</span>&gt;</span>\n          <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>\n               <span class=\"hljs-attr\">class</span>=<span class=\"hljs-string\">\"o-fluidimage\"</span>\n               <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>\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">picture</span>&gt;</span>\n\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n\n    <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>\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header</span>&gt;</span>\n        <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>\n        <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>\n        <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>\n          <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>\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">header</span>&gt;</span>\n\n      <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>\n        </span><span class=\"hljs-template-variable\">{{ content | safe }}</span><span class=\"xml\">\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">article</span>&gt;</span>\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endblock</span></span> %}</span></code></pre>\n<h3 id=\"projects\">Projects</h3>\n<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>\n<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\">\n    </span><span class=\"hljs-template-variable\">{{ project.templateContent | safe }}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<h2 id=\"satisfait-de-mon-choix\">Satisfait de mon choix</h2>\n<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>",
      "authors": [
        {
          "name": "jerome"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/06/22/cms-headless-en-3-jours/",
      "url": "https://jamstatic.fr/2019/06/22/cms-headless-en-3-jours/",
      "title": "Intégrer un CMS en 3 jours",
      "summary": "Retour d’expérience suite à l’intégration de Netlify CMS à un site Jekyll",
      "date_published": "2019-06-22T00:00:00+00:00","content_text": "\n\n\n\n\n\nImage d'illustration « netlify cms »\n\nImaginons 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 ? ».\nUne 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 Monetery, 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é : GitHub Pages.\nCette 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.\nNous devions travailler à une meilleure solution afin de migrer nos contributeurs de contenu et effectuer les changements nécessaires rapidement.\nNous avons alors étudié les options qui s’offraient à nous :\n\nMettre en place un outil de gestion de contenu (CMS) traditionnel tel que WordPress\nTrouver un CMS headless à intégrer dans un générateur de site statique (SSG)\n\nLe 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 headlesscms.org et de staticgen.com 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.\nL’une des solutions les plus intéressantes que nous avons testées vient de la société Netlify, et de son projet Netlify CMS.\nNous avons pensé que Netlify CMS pourrait être avantageux pour les raisons suivantes :\n\nIl 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\nIl est SSG agnostique, et fonctionne donc avec notre site Jekyll existant mais ne nous empêcherait pas de changer d’avis (salut GatsbyJS !)\nIl 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 sécurité informatique !\nIl fournit une expérience d’édition simple et fonctionnelle\nIl 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é\n\nSuite à 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.\nDevez-vous passer de GitHub Pages à Netlify ?\nÇ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.\nAlors pourquoi avons-nous changé d’avis et opté pour Netlify ? La réponse tient dans deux fonctionnalités très convaincantes : Git Gateway et le déploiement de branches.\nGit 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 open-source, il était clair que l’héberger nous-même allait ajouter une complexité considérable.\nLe 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.\nMigrer de GitHub Pages à Netlify\nEn 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 build, Netlify peut commencer à déployer votre site. Les tâches telles que configurer le SSL sont expliquées dans la documentation Netlify, nous ne les détaillerons donc pas ici.\nSi vous utilisez les gems intégrées à Jekyll et le processus de build proposé par GitHub, vous aurez besoin de quelques outils complémentaires pour que ça fonctionne. Vous aurez besoin d’un Gemfile pour vos dépendances, et c’est aussi une bonne idée d’intégrer la commande de build au code source :\nGemfile\nsource \"https:\/\/rubygems.org\"\ngem 'github-pages'\nnetlify.toml\n[build]\npublish = \"_site\/\"\ncommand = \"jekyll build\"\nUne 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.\nAjouter Netlify CMS à un site existant\nNetlify CMS se compose d’une application web monopage (NDT : en anglais single-page application 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 :\nadmin\n├ index.html\n└ config.yml\nLa documentation de Netlify CMS explique ça très bien :\n\nLe premier fichier, admin\/index.html, est le point d’entrée à l’admin de Netlify CMS. Cela signifie que les utilisateurs y accèdent via votresite.com\/admin\/. 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 :\n\nadmin\/index.html\n&lt;!doctype html&gt;\n&lt;html&gt;\n&lt;head&gt;\n  &lt;meta charset=\"utf-8\" \/&gt;\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/&gt;\n  &lt;title&gt;Content Manager&lt;\/title&gt;\n  &lt;script src=\"https:\/\/identity.netlify.com\/v1\/netlify-identity-widget.js\"&gt;&lt;\/script&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;!-- Include the script that builds the page and powers Netlify CMS --&gt;\n  &lt;script src=\"https:\/\/unpkg.com\/netlify-cms@^2.0.0\/dist\/netlify-cms.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n\nLe second fichier, admin\/config.yml, est le coeur de l’installation de Netlify CMS, et il est un peu plus complexe. La section Configuration rentre dans les détails.\n\nPour commencer, voici à quoi peut ressembler le fichier de configuration :\nadmin\/config.yml\nbackend:\n  name: git-gateway\n  branch: master\n  identity_url: \"https:\/\/yoursite.com\/.netlify\/identity\"\n  gateway_url: \"https:\/\/yoursite.com\/.netlify\/git\"\n  squash_merges: true\n\npublish_mode: editorial_workflow\nmedia_folder: \"assets\/img\/uploads\"\n\nsite_url: https:\/\/yoursite.com\nlogo_url: https:\/\/yoursite.com\/assets\/img\/logo.svg\n\ncollections:\nLa section backend 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é publish_mode est paramétrée de manière à ce que notre flux de travail utilise le mode editorial. En bref cela signifie que nous avons la possibilité de sauvegarder les brouillons sous la forme de Pull Requests 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é !\nRemarque : depuis mai 2019, le flux de travail éditorial n’est pris en charge que lorsque vous utilisez GitHub.\nMaintenant 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 body :\n&lt;script&gt;\n  if (window.netlifyIdentity) {\n    window.netlifyIdentity.on(\"init\", user =&gt; {\n      if (!user) {\n        window.netlifyIdentity.on(\"login\", () =&gt; {\n          document.location.href = \"\/admin\/\";\n        });\n      }\n    });\n  }\n&lt;\/script&gt;\nUne 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 https:\/\/yourdomain.com\/admin\/.\nQu’est-ce que les Collections ?\nBien 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 collections 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.\nIl existe deux types de collections, 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 back-end. 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.\nSachant 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 front matter, et quelques autres.\nParamétrage d’un fichier de collection pour les données de configuration\nUn 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 :\nadmin\/config.yml\ncollections:\n  - label: \"Options transverses\"\n    name: options\n    editor:\n      preview: false\n    files:\n      - label: \"Menu de navigation\"\n        name: nav\n        file: \"_data\/nav.yml\"\n        fields:\n          - label: \"Entrées de menu\"\n            label_singular: \"Entrée de menu\"\n            name: topLevelItems\n            widget: list\n            fields:\n              - {label: \"Texte affiché\", name: displayText, widget: string}\n              - {label: URL, name: url, widget: string}\n              - label: \"Type d'objet\"\n                name: itemType\n                widget: select\n                options: [\"Link\", \"Button\"]\nCela 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 widgets. Lorsqu'une modification est apportée, elle sera enregistrée dans le fichier _data\/nav.yml de votre projet.\n\n\n\n\n\n\nGestion du menu de navigation depuis Netlify CMS\n\nVoici un exemple de ce à quoi peut resembler un fichier de données :\n_data\/nav.yml\ntopLevelItems:\n  - displayText: 'Une page'\n    itemType: Link\n    url: '\/une-page\/'\n  - displayText: 'Lien externe'\n    itemType: Link\n    url: 'https:\/\/google.com'\nComment utiliser un fichier de collection dans Jekyll ?\nVoyons comment exploiter ces données dans un template Jekyll. Voici un template Liquid qui utilise nos données de navigation :\n&lt;ul&gt;\n  {% for item in site.data.nav.topLevelItems %}\n    &lt;li&gt;\n      {% if item.itemType == 'Link' %}\n        &lt;a href=\"{{ item.url }}\"&gt;{{ item.displayText }}&lt;\/a&gt;\n      {% else %}\n        ...\n      {% endif %}\n    &lt;\/li&gt;\n  {% endfor %}\n&lt;\/ul&gt;\nDans Jekyll, toutes les informations du dossier _data sont accessibles en utilisant la syntaxe site.data.{file}.{field}. Vous pouvez itérer et obtenir les champs que vous souhaitez.\nParamétrer un dossier de collection de pages\nUne 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.\nDans 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.\nRegardons la structure d’un dossier de collection provenant d’un fichier de configuration réel pour voir comment ça marche :\nadmin\/config.yml\ncollections:\n - label: \"Pages\"\n    label_singular: \"Page\"\n    name: pages\n    folder: \"_pages\"\n    create: true\n    slug: \"{{slug}}\"\n    preview_path: \"{{permalink}}\"\n    editor:\n      preview: false\n    fields:\n      - {label: \"Titre\", name: title, widget: string}\n      - {label: \"Lien permanent\", name: permalink, widget: string}\n      - label: \"Template\"\n        name: \"layout\"\n        widget: \"select\"\n        default: \"blocks\"\n        options:\n          - { label: \"Défaut\", value: \"blocks\" }\n          - { label: \"Page d'accueil\", value: \"home\" }\n      - {label: \"Meta description\", name: metaDescription, widget: text, required: false}\n      - label: \"Partage sur les réseaux sociaux\"\n        name: social\n        widget: object\n        required: false\n        fields:\n          - {label: \"Image OpenGraph\", name: ogImage, widget: image, required: false}\n          - {label: \"Image Twitter\", name: twitterImage, widget: image, required: false}\nCeci définit une nouvelle collection appelée “Pages” qui contiendra de nombreux fichiers stockés dans le dossier \/_pages\/ de votre projet. Les fichiers seront nommés en fonction du modèle définit dans le champ slug, lequel est configuré pour prendre la valeur de la variable pas forcément très explicite {{slug}}. Ne vous inquiétez pas, dans ce cas, cela signifie simplement que nous utiliserons la valeur par défaut, à savoir le contenu du champ Titre. 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.\n\n\n\n\n\n\nListe des pages dans Netlify CMS\n\nVeuillez noter les champs permalink et preview_path. Nous utiliserons le champ permalink pour définir le chemin d’accès à notre page dans Jekyll, et le champ de preview permet à Netlify CMS de savoir comment pointer vers la bonne URL de prévisualisation (déploiement de branches :+1:).\nVoici un exemple de ce à quoi peut ressembler le fichier de contenu d’une page :\n_pages\/home.md\n---\nTitle: Accueil\npermalink: \/\nlayout: home\nmetaDescription: Dites nous de quoi il s'agit !\nsocial: {}\n---\nComment utiliser un dossier de collection dans Jekyll ?\nSi 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 front matter. Vous pensez peut-être que c’est un peu étrange d’avoir un fichier Markdown sans contenu sous le front matter (séparé par trois tirets), mais soyez rassuré : c’est pour une bonne raison !\nNous travaillerons de concert avec la fonctionnalité de collections propre à Jekyll afin de coupler nos fichiers Markdown avec un template, lire les données du front matter et ensuite générer notre page. Cela nous permettra de faire des choses plus travaillées plus tard, comme utiliser le widget liste à types de variable pour créer un composant de page builder !\nAvant de commencer, nous avons besoin de compléter le fichier de configuration de Jekyll :\n_config.yml\ncollections:\n  pages:\n    output: true\nCeci indique à Jekyll qu’il doit générer une nouvelle page pour chaque fichier Markdown présent dans le dossier pages.\nMais comment Jekyll fait-il pour savoir quel template utiliser ? Dans le cas présent c’est champ layout défini dans Netlify CMS qui s’occupe de ça. Jekyll fait correspondre la valeur du champ dans le front matter directement avec le nom du fichier de template présent dans le dossier _layouts du projet.\nRegardons un exemple de template :\n_layouts\/home.html\n---\nlayout: default\n---\n\n&lt;h1&gt;{{ page.title }}&lt;\/h1&gt;\n\n&lt;section class=\"home\"&gt;\n  {{ content }}\n&lt;\/section&gt;\nToutes les données provenant du front matter qui nous intéresse sont accessibles en utilisant la syntaxe Jekyll {collection}.{field}. Nous pouvons utiliser les templates parents et autres comme on veut.\nRéaliser un page builder dans Jekyll\nC’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 page builder flexible, basé sur des composants.\nPour commencer, nous devons définir nos composants dans le fichier de configuration de Netlify CMS :\n_admin\/config.yml\ncollections:\n  - label: \"Pages\"\n      ...\n      - label: \"Blocs de contenu\"\n        label_singular: \"Bloc de contenu\"\n        name: blocks\n        widget: list\n        types:\n          - label: \"Mise en avant\"\n            name: hero\n            widget: object\n            fields:\n              - {label: \"En-tête\", name: heading, widget: string}\n              - {label: \"Contenu\", name: content, widget: markdown, buttons: [\"bold\", \"italic\", \"link\"], required: false}\n          - label: \"Bloc de texte riche\"\n            name: textBlock\n            widget: object\n            fields:\n              - {label: \"En-tête\", name: heading, widget: string, required: false}\n              - {label: \"Contenu\", name: content, widget: markdown}\n          ...\nIci 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.\n\n\n\n\n\n\nEdition de contenu depuis Netlify CMS\n\nCréons maintenant un nouveau template pour le rendu de nos widgets :\n_layouts\/blocks.html\n---\nlayout: default\n---\n\n{% for block in page.blocks %}\n  {% include blocks\/{{ block.type }}.html block=block %}\n{% endfor %}\nIci 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 :\n_includes\/blocks\/hero.html\n&lt;header class=\"page-hero\"&gt;\n  &lt;h1&gt;{{ block.heading }}&lt;\/h1&gt;\n  {% if block.content and block.content != '' %}\n    &lt;div class=\"max-width--330\"&gt;\n      {{ block.content | markdownify }}\n    &lt;\/div&gt;\n  {% endif %}\n&lt;\/header&gt;\nParce que nous avons transmis notre variable block, nous avons tout ce dont nous en avons besoin. Vous remarquez également que nous avons veillé à transformer notre Markdown en HTML avec markdownify car ce n’est pas fait automatiquement.\nNotre retour d'expérience avec Netlify + Netlify CMS\nGrâce à ces techniques, nos ingénieurs ont pu intégrer Netlify CMS à notre site Jekyll existant pour Monetery et mettre en oeuvre un CMS opérationnel en l’espace de quelques jours (trois pour être exact).\nLes 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 !\nCela dit, ce n’est jamais terminé. 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 :\nPour\n\nHéberger son site avec Netlify est un jeu d’enfant et nous n’avons rencontré aucun problème avec le site lui-même\nNetlify CMS à été très facile à installer sur un projet Jekyll existant et il est intuitif pour les nouveaux ingénieurs\nIl 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\nL’interface de Netlify CMS est simple et facile à prendre en main pour les contributeurs de contenu\nLe déploiement et l‘aperçu par branche est extraordinaire\nL‘offre gratuite de Netlify vous donnent la liberté d’évaluer le service avant de vous engager\nIl existe une communauté active et très utile pour Netlify CMS sur Gitter\nNetlify CMS est open-source et les contributions sont bienvenues\n\nContre\n\nNos contributeurs de contenu apprécient le flux de travail éditorial, mais n’aiment pas les nombreuses étapes nécessaires pour enregistrer et publier\nL’enregistrement et la publication sont relativement lents, parfois jusqu’à plusieurs secondes\nNous rencontrons des erreurs occasionnelles — mais frustrantes — lors de l’utilisation de l’administration du CMS\nCertains 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\nL’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 commiter sur votre dépôt Git, alors soyez prudent\nIl 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\n\nCommunauté et contribution\nLes é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 Pull Request est déjà en ligne !\nDécouvrez le code sur GitHub : https:\/\/github.com\/netlify\/netlify-cms",
      "content_html": "<figure>\n<picture title=\"Image d&#039;illustration « netlify cms »\">\n<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\">\n<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\">\n<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,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);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\">\n</picture>\n<figcaption>Image d'illustration « netlify cms »</figcaption>\n</figure>\n<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>\n<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>\n<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.\nNous devions travailler à une meilleure solution afin de migrer nos contributeurs de contenu et effectuer les changements nécessaires rapidement.</p>\n<p>Nous avons alors étudié les options qui s’offraient à nous :</p>\n<ol>\n<li>Mettre en place un outil de gestion de contenu (CMS) traditionnel tel que WordPress</li>\n<li>Trouver un CMS headless à intégrer dans un générateur de site statique (SSG)</li>\n</ol>\n<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>\n<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>\n<p>Nous avons pensé que Netlify CMS pourrait être avantageux pour les raisons suivantes :</p>\n<ul>\n<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>\n<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>\n<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>\n<li>Il fournit une expérience d’édition simple et fonctionnelle</li>\n<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>\n</ul>\n<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>\n<h2 id=\"devez-vous-passer-de-github-pages-a-netlify\">Devez-vous passer de GitHub Pages à Netlify ?</h2>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"migrer-de-github-pages-a-netlify\">Migrer de GitHub Pages à Netlify</h2>\n<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>\n<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>\n<p><strong>Gemfile</strong></p>\n<pre><code class=\"language-ruby hljs ruby\">source <span class=\"hljs-string\">\"https://rubygems.org\"</span>\ngem <span class=\"hljs-string\">'github-pages'</span></code></pre>\n<p><strong>netlify.toml</strong></p>\n<pre><code class=\"language-toml hljs ini\"><span class=\"hljs-section\">[build]</span>\n<span class=\"hljs-attr\">publish</span> = <span class=\"hljs-string\">\"_site/\"</span>\n<span class=\"hljs-attr\">command</span> = <span class=\"hljs-string\">\"jekyll build\"</span></code></pre>\n<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>\n<h2 id=\"ajouter-netlify-cms-a-un-site-existant\">Ajouter Netlify CMS à un site existant</h2>\n<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>\n<pre><code class=\"language-text\">admin\n├ index.html\n└ config.yml</code></pre>\n<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>\n<blockquote>\n<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>\n</blockquote>\n<p><strong>admin/index.html</strong></p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-meta\">&lt;!doctype <span class=\"hljs-meta-keyword\">html</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span>\n  <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>\n  <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>\n  <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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span>\n  <span class=\"hljs-comment\">&lt;!-- Include the script that builds the page and powers Netlify CMS --&gt;</span>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></code></pre>\n<blockquote>\n<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>\n</blockquote>\n<p>Pour commencer, voici à quoi peut ressembler le fichier de configuration :</p>\n<p><strong>admin/config.yml</strong></p>\n<pre><code class=\"language-yml hljs yaml\"><span class=\"hljs-attr\">backend:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">git-gateway</span>\n  <span class=\"hljs-attr\">branch:</span> <span class=\"hljs-string\">master</span>\n  <span class=\"hljs-attr\">identity_url:</span> <span class=\"hljs-string\">\"https://yoursite.com/.netlify/identity\"</span>\n  <span class=\"hljs-attr\">gateway_url:</span> <span class=\"hljs-string\">\"https://yoursite.com/.netlify/git\"</span>\n  <span class=\"hljs-attr\">squash_merges:</span> <span class=\"hljs-literal\">true</span>\n\n<span class=\"hljs-attr\">publish_mode:</span> <span class=\"hljs-string\">editorial_workflow</span>\n<span class=\"hljs-attr\">media_folder:</span> <span class=\"hljs-string\">\"assets/img/uploads\"</span>\n\n<span class=\"hljs-attr\">site_url:</span> <span class=\"hljs-string\">https://yoursite.com</span>\n<span class=\"hljs-attr\">logo_url:</span> <span class=\"hljs-string\">https://yoursite.com/assets/img/logo.svg</span>\n\n<span class=\"hljs-attr\">collections:</span></code></pre>\n<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>\n<p><em>Remarque : depuis mai 2019, le flux de travail éditorial n’est pris en charge que lorsque vous utilisez GitHub.</em></p>\n<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>\n<pre><code class=\"language-javascript hljs javascript\">&lt;script&gt;\n  <span class=\"hljs-keyword\">if</span> (<span class=\"hljs-built_in\">window</span>.netlifyIdentity) {\n    <span class=\"hljs-built_in\">window</span>.netlifyIdentity.on(<span class=\"hljs-string\">\"init\"</span>, user =&gt; {\n      <span class=\"hljs-keyword\">if</span> (!user) {\n        <span class=\"hljs-built_in\">window</span>.netlifyIdentity.on(<span class=\"hljs-string\">\"login\"</span>, () =&gt; {\n          <span class=\"hljs-built_in\">document</span>.location.href = <span class=\"hljs-string\">\"/admin/\"</span>;\n        });\n      }\n    });\n  }\n&lt;<span class=\"hljs-regexp\">/script&gt;</span></code></pre>\n<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>\n<h3 id=\"qu-est-ce-que-les-collections\">Qu’est-ce que les Collections ?</h3>\n<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>\n<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>\n<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>\n<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>\n<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>\n<p><strong>admin/config.yml</strong></p>\n<pre><code class=\"language-yml hljs yaml\"><span class=\"hljs-attr\">collections:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Options transverses\"</span>\n    <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">options</span>\n    <span class=\"hljs-attr\">editor:</span>\n      <span class=\"hljs-attr\">preview:</span> <span class=\"hljs-literal\">false</span>\n    <span class=\"hljs-attr\">files:</span>\n      <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Menu de navigation\"</span>\n        <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">nav</span>\n        <span class=\"hljs-attr\">file:</span> <span class=\"hljs-string\">\"_data/nav.yml\"</span>\n        <span class=\"hljs-attr\">fields:</span>\n          <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Entrées de menu\"</span>\n            <span class=\"hljs-attr\">label_singular:</span> <span class=\"hljs-string\">\"Entrée de menu\"</span>\n            <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">topLevelItems</span>\n            <span class=\"hljs-attr\">widget:</span> <span class=\"hljs-string\">list</span>\n            <span class=\"hljs-attr\">fields:</span>\n              <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>\n              <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>\n              <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Type d'objet\"</span>\n                <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">itemType</span>\n                <span class=\"hljs-attr\">widget:</span> <span class=\"hljs-string\">select</span>\n                <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>\n<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>\n<figure>\n<picture title=\"Gestion du menu de navigation depuis Netlify CMS\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Gestion du menu de navigation depuis Netlify CMS</figcaption>\n</figure>\n<p>Voici un exemple de ce à quoi peut resembler un fichier de données :</p>\n<p><strong>_data/nav.yml</strong></p>\n<pre><code class=\"language-yml hljs yaml\"><span class=\"hljs-attr\">topLevelItems:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">displayText:</span> <span class=\"hljs-string\">'Une page'</span>\n    <span class=\"hljs-attr\">itemType:</span> <span class=\"hljs-string\">Link</span>\n    <span class=\"hljs-attr\">url:</span> <span class=\"hljs-string\">'/une-page/'</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">displayText:</span> <span class=\"hljs-string\">'Lien externe'</span>\n    <span class=\"hljs-attr\">itemType:</span> <span class=\"hljs-string\">Link</span>\n    <span class=\"hljs-attr\">url:</span> <span class=\"hljs-string\">'https://google.com'</span></code></pre>\n<h3 id=\"comment-utiliser-un-fichier-de-collection-dans-jekyll\">Comment utiliser un fichier de collection dans Jekyll ?</h3>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n  {% for item in site.data.nav.topLevelItems %}\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li</span>&gt;</span>\n      {% if item.itemType == 'Link' %}\n        <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>\n      {% else %}\n        ...\n      {% endif %}\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n  {% endfor %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span></code></pre>\n<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>\n<h3 id=\"parametrer-un-dossier-de-collection-de-pages\">Paramétrer un dossier de collection de pages</h3>\n<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.\nDans 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>\n<p>Regardons la structure d’un dossier de collection provenant d’un fichier de configuration réel pour voir comment ça marche :</p>\n<p><strong>admin/config.yml</strong></p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">collections:</span>\n <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Pages\"</span>\n    <span class=\"hljs-attr\">label_singular:</span> <span class=\"hljs-string\">\"Page\"</span>\n    <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">pages</span>\n    <span class=\"hljs-attr\">folder:</span> <span class=\"hljs-string\">\"_pages\"</span>\n    <span class=\"hljs-attr\">create:</span> <span class=\"hljs-literal\">true</span>\n    <span class=\"hljs-attr\">slug:</span> <span class=\"hljs-string\">\"<span class=\"hljs-template-variable\">{{slug}}</span>\"</span>\n    <span class=\"hljs-attr\">preview_path:</span> <span class=\"hljs-string\">\"<span class=\"hljs-template-variable\">{{permalink}}</span>\"</span>\n    <span class=\"hljs-attr\">editor:</span>\n      <span class=\"hljs-attr\">preview:</span> <span class=\"hljs-literal\">false</span>\n    <span class=\"hljs-attr\">fields:</span>\n      <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>\n      <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>\n      <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Template\"</span>\n        <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">\"layout\"</span>\n        <span class=\"hljs-attr\">widget:</span> <span class=\"hljs-string\">\"select\"</span>\n        <span class=\"hljs-attr\">default:</span> <span class=\"hljs-string\">\"blocks\"</span>\n        <span class=\"hljs-attr\">options:</span>\n          <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>\n          <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>\n      <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>\n      <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Partage sur les réseaux sociaux\"</span>\n        <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">social</span>\n        <span class=\"hljs-attr\">widget:</span> <span class=\"hljs-string\">object</span>\n        <span class=\"hljs-attr\">required:</span> <span class=\"hljs-literal\">false</span>\n        <span class=\"hljs-attr\">fields:</span>\n          <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>\n          <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>\n<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>\n<figure>\n<picture title=\"Liste des pages dans Netlify CMS\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Liste des pages dans Netlify CMS</figcaption>\n</figure>\n<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>\n<p>Voici un exemple de ce à quoi peut ressembler le fichier de contenu d’une page :</p>\n<p><strong>_pages/home.md</strong></p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">Title:</span> <span class=\"hljs-string\">Accueil</span>\n<span class=\"hljs-attr\">permalink:</span> <span class=\"hljs-string\">/</span>\n<span class=\"hljs-attr\">layout:</span> <span class=\"hljs-string\">home</span>\n<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>\n<span class=\"hljs-attr\">social:</span> <span class=\"hljs-string\">{}</span>\n<span class=\"hljs-meta\">---</span></code></pre>\n<h3 id=\"comment-utiliser-un-dossier-de-collection-dans-jekyll\">Comment utiliser un dossier de collection dans Jekyll ?</h3>\n<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>\n<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>\n<p>Avant de commencer, nous avons besoin de compléter le fichier de configuration de Jekyll :</p>\n<p><strong>_config.yml</strong></p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">collections:</span>\n  <span class=\"hljs-attr\">pages:</span>\n    <span class=\"hljs-attr\">output:</span> <span class=\"hljs-literal\">true</span></code></pre>\n<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>\n<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>\n<p>Regardons un exemple de template :</p>\n<p><strong>_layouts/home.html</strong></p>\n<pre><code class=\"language-html hljs xml\">---\nlayout: default\n---\n\n<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>\n\n<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>\n  {{ content }}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">section</span>&gt;</span></code></pre>\n<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>\n<h3 id=\"realiser-un-page-builder-dans-jekyll\">Réaliser un <em>page builder</em> dans Jekyll</h3>\n<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>\n<p>Pour commencer, nous devons définir nos composants dans le fichier de configuration de Netlify CMS :</p>\n<p><strong>_admin/config.yml</strong></p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">collections:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Pages\"</span>\n      <span class=\"hljs-string\">...</span>\n      <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Blocs de contenu\"</span>\n        <span class=\"hljs-attr\">label_singular:</span> <span class=\"hljs-string\">\"Bloc de contenu\"</span>\n        <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">blocks</span>\n        <span class=\"hljs-attr\">widget:</span> <span class=\"hljs-string\">list</span>\n        <span class=\"hljs-attr\">types:</span>\n          <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Mise en avant\"</span>\n            <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">hero</span>\n            <span class=\"hljs-attr\">widget:</span> <span class=\"hljs-string\">object</span>\n            <span class=\"hljs-attr\">fields:</span>\n              <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>\n              <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>\n          <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">label:</span> <span class=\"hljs-string\">\"Bloc de texte riche\"</span>\n            <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">textBlock</span>\n            <span class=\"hljs-attr\">widget:</span> <span class=\"hljs-string\">object</span>\n            <span class=\"hljs-attr\">fields:</span>\n              <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>\n              <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>\n          <span class=\"hljs-string\">...</span></code></pre>\n<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>\n<figure>\n<picture title=\"Edition de contenu depuis Netlify CMS\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Edition de contenu depuis Netlify CMS</figcaption>\n</figure>\n<p>Créons maintenant un nouveau template pour le rendu de nos widgets :</p>\n<p><strong>_layouts/blocks.html</strong></p>\n<pre><code class=\"language-html hljs xml\">---\nlayout: default\n---\n\n{% for block in page.blocks %}\n  {% include blocks/{{ block.type }}.html block=block %}\n{% endfor %}</code></pre>\n<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>\n<p><strong>_includes/blocks/hero.html</strong></p>\n<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>\n  <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>\n  {% if block.content and block.content != '' %}\n    <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>\n      {{ block.content | markdownify }}\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  {% endif %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">header</span>&gt;</span></code></pre>\n<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>\n<h2 id=\"notre-retour-d-experience-avec-netlify-netlify-cms\">Notre retour d'expérience avec Netlify + Netlify CMS</h2>\n<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).\nLes 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>\n<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>\n<h3 id=\"潰牵\">Pour</h3>\n<ul>\n<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>\n<li>Netlify CMS à été très facile à installer sur un projet Jekyll existant et il est intuitif pour les nouveaux ingénieurs</li>\n<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>\n<li>L’interface de Netlify CMS est simple et facile à prendre en main pour les contributeurs de contenu</li>\n<li>Le déploiement et l‘aperçu par branche est extraordinaire</li>\n<li>L‘offre gratuite de Netlify vous donnent la liberté d’évaluer le service avant de vous engager</li>\n<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>\n<li>Netlify CMS est open-source et les contributions sont bienvenues</li>\n</ul>\n<h3 id=\"contre\">Contre</h3>\n<ul>\n<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>\n<li>L’enregistrement et la publication sont relativement lents, parfois jusqu’à plusieurs secondes</li>\n<li>Nous rencontrons des erreurs occasionnelles — mais frustrantes — lors de l’utilisation de l’administration du CMS</li>\n<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>\n<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>\n<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>\n</ul>\n<h2 id=\"communaute-et-contribution\">Communauté et contribution</h2>\n<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>\n<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>",
      "authors": [
        {
          "name": "arnaud"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/03/01/gatsby-pour-les-applis-web/",
      "url": "https://jamstatic.fr/2019/03/01/gatsby-pour-les-applis-web/",
      "title": "Gatsby pour les applis Web",
      "summary": "Comment Gatsby permet d'allier les atouts du statique avec les possibilités du dynamique pour créer des applis Web riches en fonctionnalités.",
      "date_published": "2019-03-01T10:00:00+00:00","content_text": "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.\nDans 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.\nNous 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.\nPour commencer, nous pouvons nous demander ce qu'est, au juste, une appli Web.\n\nNote :\nSi vous ne l'avez pas déjà fait, nous vous suggérons de regarder la vidéo « Beyond Static: Building Dynamic Apps with Gatsby » de laquelle la plupart des idées de ce billet sont reprises.\n\nQu'est-ce qu'une appli Web ?\nJ'ai par le passé tenté de définir 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 :\n\nrequêtage de données dynamiques;\nauthentification des utilisateurs, et le fait d'avoir des routes authentifiées côté client;\ndes interactions côté client portées par JavaScript.\n\nBien 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’observer 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.\nJe 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.\nGmail\n\n\n\n\n\nGmail peut-être vu, avec le recul, comme une “preuve de concept” qui a démontré deux choses très importantes :\n\nle JavaScript côté client peut offrir une expérience proche de celles des apps, et\nune application JavaScript (qui s'exécute dans votre navigateur) soutient favorablement la comparaison avec une application native, que ce soit sur desktop ou mobile.\n\nIl ne faut pas sous-estimer l'impact de ces bénéfices. Gmail a prouvé 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.\nTwitter (Progressive Web Application)\n\n\n\n\n\nTwitter 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 :\n\nça illustre une partie de la richesse d'une expérience Web moderne, et\nça repose sur des pratiques avancées et des optimisations poussées de performances pour proposer une expérience rapide et plaisante.\n\nEn particulier, les fonctionnalités suivantes sont des composantes clés pour un nouveau genre d'applications :\n\nle cache agressif des données et une navigation rapide grâce aux Service Workers,\nla mise en place du pattern PRPL (Push, Render, Pre-cache, Lazy-Loading),\nl'illustration du pattern Coquille Applicative (App Shell) pour rendre encore plus rapide les visites subséquentes et afficher une page la plus complète possible, le plus rapidement possible.\n\nCes 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 étude de cas par Addy Osmani de Google.\nCes deux applis Web serviront de d'exemples à garder à l'esprit quand nous allons aborder l'utilisation de Gatsby pour les applis Web.\nGatsby est taillé pour les applis Web\n\n\n\n\n\nEt 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 ?\nAucune 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 :\n\ninjecte la donnée dans les pages (depuis GraphQL ou même sans utiliser GraphQL);\nutilise la fonction d'API de rendu côté serveur ReactDOMServer.renderToString pour transformer les composants React en fichiers HTML;\ninjecte le runtime et des additions (comme un routeur !) pour permettre de construire des features d’applis Web.\n\n\nGatsby offre une expérience similaire à create-react-app une fois que le runtime est fonctionnel\n\nPour 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.\n\/\/ src\/pages\/messages.js\n\nimport React from \"react\";\n\nimport Layout from \"..\/components\/layout\";\n\nclass Messages extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      messages: [],\n    };\n  }\n\n  \/\/ note: this is a simplified example without error handling, authentication, etc.\n  async componentDidMount() {\n    const messages = await fetch(\n      `\/api\/some-url-to-get-messages`\n    ).then((response) =&gt; response.json());\n\n    this.setState({\n      messages,\n    });\n  }\n\n  render() {\n    const { messages } = this.state;\n    return (\n      &lt;Layout&gt;\n        {messages.length === 0 ? (\n          &lt;p&gt;Loading messages&amp;hellip;&lt;\/p&gt;\n        ) : (\n          &lt;ul&gt;\n            {messages.map((message) =&gt; (\n              &lt;li key={message.id}&gt;{message.text}&lt;\/li&gt;\n            ))}\n          &lt;\/ul&gt;\n        )}\n      &lt;\/Layout&gt;\n    );\n  }\n}\n\nexport default Messages;\nTout ce que nous avons fait ci-dessus est d'implémenter la méthode du cycle de vie React componentDidMount, 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 create-react-app déjà hydraté avec la donnée. Les méthodes du lifecycle React 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.\nConsidé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 !\n\nCependant, 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 nécessitait une authentification ? Et si nous souhaitions des routes gérées côté client, 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 et du runtime ? Bien sûr !\nL'idée principale que je tiens à clarifier dans cet article est que les fonctionnalités typiques 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 que du React.\nGatsby 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.\nPourquoi utiliser Gatsby pour une appli Web ?\nDes optimisations de performance, par défaut\nSi l'on regarde les avantages que procure Gatsby, à minima :\n\nle rendu statique de composants React et des données associées en HTML statique;\nl'optimisation des données, des images, etc. pour obtenir des sites ultra-rapides;\nla présence de patterns liés à la performance et des bonnes pratiques comme le PRPL, la séparation de code par route, etc.\n\nChacun de ces avantages mériteraient que l'on s'y attarde un peu plus, mais nous nous contenterons de dire que ce sont d' excellentes fonctionnalités que vous souhaitez avoir dans vos sites statiques, mais aussi dans vos applis web.\nCes 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 create-react-app.\nLes plugins et l'écosystème Gatsby\nUn des avantages principaux de Gatsby est son architecture modulaire. Vous avez besoin d'un plugin pour récupérer des données de Wordpress ? Bien sûr, cela semble raisonnable. Vous avez besoin de transformer des données YAML en objets JavaScript plus simples à manipuler ? Allez, pourquoi pas ! Une envie de se raccorder à une API GraphQL distante 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 !\nPrenons le temps de regarder plus en détail ces fonctionnalités proposées par un de nos composants, gatsby-image.\ngatsby-image\ngatsby-image 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 gatsby-plugin-sharp. 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 :\n\nl'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;\nla génération de multiples images, adaptées aux appareils mobiles, en utilisant srcset pour ne télécharger que celle dont votre utilisateur a besoin;\nle chargement asynchrone des images avec une technique de flou dégressif — ou même de SVG tracé.\n\ngatsby-image 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é, “Using Gatsby Image” (en anglais) pour en apprendre plus et le voir dans un cas d'utilisation réelle. En somme, utilisez gatsby-image et vos utilisateurs vous remercieront.\nLe 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 librairie de plugins si ce n'est pas déjà fait !\nMaintenant, 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.\nLa Coquille Applicative\nEn ajoutant juste le plugin gatsby-plugin-offline, 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.\nSi nous regardons de plus près cette approche, elle se décompose comme suit:\n\non rend le plus de contenu possible instantanément (la coquille applicative);\non 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).\n\nComparons 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.\nPour 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.\n\nNous 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 toute la donnée 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 est très rapide grâce aux optimisations que l'on obtient avec Gatsby, par défaut.\nAfin 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 possibles avec Gatsby, mais aussi que Gatsby est un excellent choix lorsque l'on doit en construire une.\nEt voici… Gatsby Mail ! (Qui ne sert qu'à des fins de démonstration !)\n\n\n\n\n\nGatsby Mail reprend certains des concepts et des thèmes que j'ai abordés. Plus particulièrement :\n\nGmail, Twitter et les autres sont des exemples parlants d'applis Web complexes qui proposent des expériences riches.\nGatsby fournit des composants, des plugins, etc… qui permettent de développer ces expériences, servez-vous en !\nGatsby est un excellent choix lorsqu'il s'agit de construire de telles applis Web.\n\nDe plus, Gatsby Mail fait la démonstration de fonctionnalités spécifiques aux applis Web, comme :\n\ndu rendu statique couplé à des requêtes dynamiques grâce au runtime côté client;\nde l'authentification et des routes gérées côté client;\ndes routes non-authentifiées, comme une page d'accueil (qui utilise l'API Context de React);\nl'utilisation de GraphQL au moment du build et au runtime, en s'appuyant sur une API GraphQL distante et sur apollo-boost;\nle chargement d'une coquille applicative grâce à gatsby-plugin-offline (voyez par vous-même la démo en “3G rapide” ci-dessous !).\n\net 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 instantanément pendant que l'on va chercher le contenu dynamique (depuis notre API distante GraphQL) en tâche de fond.\n\nVous pouvez aller jeter un oeil au repository GitHub pour en savoir plus sur comment elle a été développée, et vous approprier quelques unes de ces techniques pour la prochaine appli Web que vous développerez en Gatsby ;)\nOn a hâte de voir ce que vous allez construire.",
      "content_html": "<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>\n<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>\n<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>\n<p>Pour commencer, nous pouvons nous demander ce qu'est, au juste, une appli Web.</p>\n<blockquote>\n<p>Note :\nSi 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>\n</blockquote>\n<h2 id=\"qu-est-ce-qu-une-appli-web\">Qu'est-ce qu'une appli Web ?</h2>\n<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>\n<ul>\n<li>requêtage de données dynamiques;</li>\n<li>authentification des utilisateurs, et le fait d'avoir des routes authentifiées côté client;</li>\n<li>des interactions côté client portées par JavaScript.</li>\n</ul>\n<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>\n<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>\n<h2 id=\"gmail\">Gmail</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<p>Gmail peut-être vu, avec le recul, comme une “preuve de concept” qui a démontré deux choses très importantes :</p>\n<ul>\n<li>le JavaScript côté client peut offrir une expérience proche de celles des apps, et</li>\n<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>\n</ul>\n<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>\n<h2 id=\"twitter-progressive-web-application\">Twitter (Progressive Web Application)</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<ul>\n<li>ça illustre une partie de la richesse d'une expérience Web moderne, et</li>\n<li>ça repose sur des pratiques avancées et des optimisations poussées de performances pour proposer une expérience rapide et plaisante.</li>\n</ul>\n<p>En particulier, les fonctionnalités suivantes sont des composantes clés pour un nouveau genre d'applications :</p>\n<ul>\n<li>le cache agressif des données et une navigation rapide grâce aux Service Workers,</li>\n<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>\n<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>\n</ul>\n<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>\n<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>\n<h2 id=\"gatsby-est-taille-pour-les-applis-web\">Gatsby est taillé pour les applis Web</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<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>\n<ol>\n<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>\n<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>\n<li>injecte le runtime et des additions (comme un routeur !) pour permettre de construire des features d’applis Web.</li>\n</ol>\n<ul>\n<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>\n</ul>\n<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>\n<pre><code class=\"language-jsx hljs javascript\"><span class=\"hljs-comment\">// src/pages/messages.js</span>\n\n<span class=\"hljs-keyword\">import</span> React <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"react\"</span>;\n\n<span class=\"hljs-keyword\">import</span> Layout <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"../components/layout\"</span>;\n\n<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>{\n  <span class=\"hljs-keyword\">constructor</span>(props) {\n    <span class=\"hljs-keyword\">super</span>(props);\n\n    <span class=\"hljs-keyword\">this</span>.state = {\n      <span class=\"hljs-attr\">messages</span>: [],\n    };\n  }\n\n  <span class=\"hljs-comment\">// note: this is a simplified example without error handling, authentication, etc.</span>\n  <span class=\"hljs-keyword\">async</span> componentDidMount() {\n    <span class=\"hljs-keyword\">const</span> messages = <span class=\"hljs-keyword\">await</span> fetch(\n      <span class=\"hljs-string\">`/api/some-url-to-get-messages`</span>\n    ).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">response</span>) =&gt;</span> response.json());\n\n    <span class=\"hljs-keyword\">this</span>.setState({\n      messages,\n    });\n  }\n\n  render() {\n    <span class=\"hljs-keyword\">const</span> { messages } = <span class=\"hljs-keyword\">this</span>.state;\n    <span class=\"hljs-keyword\">return</span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Layout</span>&gt;</span>\n        {messages.length === 0 ? (\n          <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>\n        ) : (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n            {messages.map((message) =&gt; (\n              <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>\n            ))}\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span>\n        )}\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">Layout</span>&gt;</span></span>\n    );\n  }\n}\n\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> Messages;</code></pre>\n<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>\n<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>\n<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=\"\">\n<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>\n<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>\n<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>\n<h2 id=\"pourquoi-utiliser-gatsby-pour-une-appli-web\">Pourquoi utiliser Gatsby pour une appli Web ?</h2>\n<p><strong>Des optimisations de performance, par défaut</strong></p>\n<p>Si l'on regarde les avantages que procure Gatsby, à minima :</p>\n<ul>\n<li>le rendu statique de composants React et des données associées en HTML statique;</li>\n<li>l'optimisation des données, des images, etc. pour obtenir des sites ultra-rapides;</li>\n<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>\n</ul>\n<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>\n<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>\n<p><strong>Les plugins et l'écosystème Gatsby</strong></p>\n<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>\n<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>\n<p><strong><code>gatsby-image</code></strong></p>\n<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>\n<ul>\n<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>\n<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>\n<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>\n</ul>\n<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>\n<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>\n<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>\n<h2 id=\"la-coquille-applicative\">La Coquille Applicative</h2>\n<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>\n<p>Si nous regardons de plus près cette approche, elle se décompose comme suit:</p>\n<ul>\n<li>on rend le plus de contenu possible instantanément (la coquille applicative);</li>\n<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>\n</ul>\n<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>\n<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>\n<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=\"\">\n<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>\n<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>\n<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>\n<picture>\n<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\">\n<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\">\n<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,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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/gatsby-mail.24b7eea294e201aa9918deda40d84220.png 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail.24b7eea294e201aa9918deda40d84220.png 1024w\" sizes=\"100vw\">\n</picture>\n<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>\n<ol>\n<li>Gmail, Twitter et les autres sont des exemples parlants d'applis Web complexes qui proposent des expériences riches.</li>\n<li>Gatsby fournit des composants, des plugins, etc… qui permettent de développer ces expériences, servez-vous en !</li>\n<li>Gatsby est un excellent choix lorsqu'il s'agit de construire de telles applis Web.</li>\n</ol>\n<p>De plus, Gatsby Mail fait la démonstration de fonctionnalités spécifiques aux applis Web, comme :</p>\n<ul>\n<li>du rendu statique couplé à des requêtes dynamiques grâce au <em>runtime</em> côté client;</li>\n<li>de l'authentification et des routes gérées côté client;</li>\n<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>\n<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>\n<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>\n</ul>\n<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>\n<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=\"\">\n<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>\n<p>On a hâte de voir ce que vous allez construire.</p>",
      "authors": [
        {
          "name": "phacks"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/02/07/c-est-quoi-la-jamstack/",
      "url": "https://jamstatic.fr/2019/02/07/c-est-quoi-la-jamstack/",
      "title": "C'est quoi la Jamstack au juste ?",
      "summary": "Un petit pense-bête pour expliquer les concepts de la Jamstack et encourager davantage de personnes à adopter cette approche.",
      "date_published": "2019-02-07T19:12:14+00:00",
      "date_modified": "2019-02-28T08:17:42+00:00","content_text": "Pedro Duarte a lancé https:\/\/jamstack.wtf 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.\nLa 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é.\nVous vous demandez peut-être ; oui OK, mais comment ? pourquoi ? c'est quoi au juste ?\nC'est la raison d'être de cette page https:\/\/jamstack.wtf.\nLe 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.\nLe contenu ci-dessous est tiré du site ci-dessus 👆\nAsseyez-vous, mettez-vous à l'aise et appréciez ✌️\n\n\nC'est quoi la Jamstack ?\nSignification\nBénéfices\nBonnes pratiques\nChaîne de publication\nHistorique\n\n\nBien démarrer\nDéveloppement\nDéploiement\nParties dynamiques\nCMS\nRessources\n\n\nÀ propos\n\n\nC'est quoi la Jamstack ?\nSignification\n\n\nJAM c'est pour JavaScript, APIs &amp; Markup.\n\nJavaScript\nLes fonctionnalités dynamiques sont gérées par JavaScript. Vous êtes libres d'utiliser la bibliothèque ou le framework que vous voulez.\nAPIs\nLes 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.\nMarkup\nLes 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.\nBénéfices\nLes principaux bénéfices apportés par la Jamstack sont :\nUne performance accrue\nServir du code généré et des assets à partir d'un CDN\nUne meilleure sécurité\nPlus besoin de se soucier des vulnérabilités du serveur ou de la base de données\nUn coût bien moindre\nL'hébergement de fichiers statiques est moins cher voire gratuit\nUne meilleure expérience de développement\nLes 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é.\nRedimensionnement à la volée\nSi votre site devient viral ou est soumis à un pic d'activité, le CDN compensera sans problèmes.\nBonnes pratiques\nLes astuces suivantes vous aideront à tirer le meilleur parti de la stack.\nRéseau de distribution de contenu (CDN)\nPuisque 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.\nEn savoir plus\nDéploiement atomique\nChaque déploiement est une photographie complète du site. Vous disposez ainsi d'une version consistante du site à l'échelle mondiale.\nEn savoir plus\nInvalidation du cache\nUne 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.\nEn savoir plus\nTout est versionné\nVotre 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é.\nEn savoir plus\nGénérations automatiques\nVotre 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.\nEn savoir plus\nChaîne de publication\nVoici à quoi ressemblerait la chaîne de publication Jamstack idéale.\n\n\nChaine de publication\n\nHistorique\n2015\nLes générateurs statiques sont de plus en plus en vogue, grâce à des générateurs populaires comme Jekyll.\n2016\nQuelques développeurs pensent que les sites statiques n'ont pas à être forcément statiques, le terme \"Jamstack\" fait son apparition.\n2017\nLa 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.\n2018\nDes 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.\nSource : Snipcart\n\nBien démarrer\nC'est à vous de décider comment générer vos fichiers HTML. Les trois approches les plus communes sont :\nDéveloppement\nÀ la main\nUne méthode simple et efficace d'écrire du HTML, c'est idéal pour les pages super simples.\nGénérateurs de site statique\nLa plupart des sites Jamstack sont propulsés par un générateur de site statique.\nVous êtes libres de choisir votre GSS.\n\nGatsby\nNext.js\nHugo\n\nVoir davantage de générateurs\nFramework frontend\nLa 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.\n\nReact\nVue.js\nPreact\n\nDéploiement\nVous devez héberger le résultat de la compilation de votre site. Il existe de fantastiques services qui font cela gratuitement et simplement.\n\nNetlify\nVercel\nGithub Pages\n\nVoir plus de services de déploiement\nParties dynamiques\nLes 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.\nFonctions personnalisées\nVous pouvez également abstraire vos propres fonctions pour en faire des APIs réutilisables. Pour cela vous pouvez utiliser les fonctions AWS lambda ou les fonctions Netlify\nCommentaires\nBeaucoup de sites Jamstack intègrent des sections pour les commentaires, principalement sur des blogs.\nFormulaires\nUn excellent moyen d'interagir avec votre audience.\nE-Commerce\nMettre en place une boutique en ligne sur un site Jamstack n'a jamais été aussi simple.\nRecherche\nReposez-vous sur des services tiers pour intégrer des fonctionnalités de recherche.\nVoir plus de services pour les sites statiques\nCMS\nLes 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.\n\nContentful\nForestry\nGhost\nHeadless WordPress\nNetlify CMS\nStrapi\n\nVoir plus de services de gestion de contenu\nRessources\nVoici une sélection de ressources sur la Jamstack qui comporte des matériaux d'apprentissage ainsi que des listes de services.\nServices\n\nUne liste de services pour les sites web statiques\nUne liste de gestionnaires de contenu pour les sites Jamstack\nUne liste de générateurs de site statiques pour les sites Jamstack\nUn annuaire de sélection d'outils et de services\n\nArticles\n\nDébuter avec la Jamstack? Tout ce que vous devez savoir pour bien démarrer\nQuel est le concept derrière la Jamstack\nDéveloppement web moderne avec la Jamstack\nSmashing Magazine va dix fois plus vite\nGhost avec la Jamstack\nJamstack avec Gatsby, Netlify et Netlify CMS\n\nVidéos\n\nL'essor de la Jamstack, présentation de Mathias Biilmann\nLa nouvelle stack Front-end, présentation de Mathias Biilmann\nUne sélection de vidéos par The New Dynamic\nComment freeCodeCamp sert des millions d'apprenants en utilisant la Jamstack\n\nPodcast\n\nJamstack Radio\n\n\nÀ propos\nCette page a été mise en place par @peduarte et présentée au meetup Jamstack de Londres — (voir les slides).",
      "content_html": "<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>\n<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>\n<p>Vous vous demandez peut-être ; oui OK, mais comment ? pourquoi ? c'est quoi au juste ?</p>\n<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>\n<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>\n<p>Le contenu ci-dessous est tiré du site ci-dessus 👆</p>\n<p>Asseyez-vous, mettez-vous à l'aise et appréciez ✌️</p>\n<hr>\n<div id=\"toc\"><ul>\n<li><a href=\"#c-est-quoi-la-jamstack\">C'est quoi la Jamstack ?</a><ul>\n<li><a href=\"#signification\">Signification</a></li>\n<li><a href=\"#benefices\">Bénéfices</a></li>\n<li><a href=\"#bonnes-pratiques\">Bonnes pratiques</a></li>\n<li><a href=\"#chaine-de-publication\">Chaîne de publication</a></li>\n<li><a href=\"#historique\">Historique</a></li>\n</ul>\n</li>\n<li><a href=\"#bien-demarrer\">Bien démarrer</a><ul>\n<li><a href=\"#developpement\">Développement</a></li>\n<li><a href=\"#deploiement\">Déploiement</a></li>\n<li><a href=\"#parties-dynamiques\">Parties dynamiques</a></li>\n<li><a href=\"#cms\">CMS</a></li>\n<li><a href=\"#ressources\">Ressources</a></li>\n</ul>\n</li>\n<li><a href=\"#a-propos\">À propos</a></li>\n</ul></div>\n<hr>\n<h2 id=\"c-est-quoi-la-jamstack\">C'est quoi la Jamstack ?</h2>\n<h3 id=\"signification\">Signification</h3>\n<figure>\n<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\">\n<figcaption>JAM c'est pour JavaScript, APIs &amp; Markup.</figcaption>\n</figure>\n<p><strong>JavaScript</strong><br>\nLes fonctionnalités dynamiques sont gérées par JavaScript. Vous êtes libres d'utiliser la bibliothèque ou le framework que vous voulez.</p>\n<p><strong>APIs</strong><br>\nLes 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>\n<p><strong>Markup</strong><br>\nLes 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>\n<h3 id=\"benefices\">Bénéfices</h3>\n<p>Les principaux bénéfices apportés par la Jamstack sont :</p>\n<p><strong>Une performance accrue</strong><br>\nServir du code généré et des assets à partir d'un CDN</p>\n<p><strong>Une meilleure sécurité</strong><br>\nPlus besoin de se soucier des vulnérabilités du serveur ou de la base de données</p>\n<p><strong>Un coût bien moindre</strong><br>\nL'hébergement de fichiers statiques est moins cher <a href=\"https://netlify.com\" target=\"_blank\" rel=\"noopener noreferrer\">voire gratuit</a></p>\n<p><strong>Une meilleure expérience de développement</strong><br>\nLes 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>\n<p><strong>Redimensionnement à la volée</strong><br>\nSi votre site devient viral ou est soumis à un pic d'activité, le CDN compensera sans problèmes.</p>\n<h3 id=\"bonnes-pratiques\">Bonnes pratiques</h3>\n<p>Les astuces suivantes vous aideront à tirer le meilleur parti de la stack.</p>\n<p><strong>Réseau de distribution de contenu (CDN)</strong><br>\nPuisque 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>\n<p><a href=\"https://www.cloudflare.com/learning/cdn/what-is-a-cdn/\" target=\"_blank\" rel=\"noopener noreferrer\">En savoir plus</a></p>\n<p><strong>Déploiement atomique</strong><br>\nChaque déploiement est une photographie complète du site. Vous disposez ainsi d'une version consistante du site à l'échelle mondiale.</p>\n<p><a href=\"https://buddy.works/blog/introducing-atomic-deployments#what-are-atomic-deployments\" target=\"_blank\" rel=\"noopener noreferrer\">En savoir plus</a></p>\n<p><strong>Invalidation du cache</strong><br>\nUne 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>\n<p><a href=\"https://www.netlify.com/blog/2015/09/11/instant-cache-invalidation/\" target=\"_blank\" rel=\"noopener noreferrer\">En savoir plus</a></p>\n<p><strong>Tout est versionné</strong><br>\nVotre 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>\n<p><a href=\"https://www.atlassian.com/git/tutorials/what-is-version-control\" target=\"_blank\" rel=\"noopener noreferrer\">En savoir plus</a></p>\n<p><strong>Générations automatiques</strong><br>\nVotre 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>\n<p><a href=\"https://www.agilealliance.org/glossary/automated-build\" target=\"_blank\" rel=\"noopener noreferrer\">En savoir plus</a></p>\n<h3 id=\"chaine-de-publication\">Chaîne de publication</h3>\n<p>Voici à quoi ressemblerait la chaîne de publication Jamstack idéale.</p>\n<figure>\n<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\">\n<figcaption>Chaine de publication</figcaption>\n</figure>\n<h3 id=\"historique\">Historique</h3>\n<p><strong>2015</strong><br>\nLes générateurs statiques sont de plus en plus en vogue, grâce à des générateurs populaires comme Jekyll.</p>\n<p><strong>2016</strong><br>\nQuelques développeurs pensent que les sites statiques n'ont pas à être forcément statiques, le terme \"Jamstack\" fait son apparition.</p>\n<p><strong>2017</strong><br>\nLa 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>\n<p><strong>2018</strong><br>\nDes 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>\n<p><a href=\"https://snipcart.com/blog/jamstack\" target=\"_blank\" rel=\"noopener noreferrer\">Source : Snipcart</a></p>\n<hr>\n<h2 id=\"bien-demarrer\">Bien démarrer</h2>\n<p>C'est à vous de décider comment générer vos fichiers HTML. Les trois approches les plus communes sont :</p>\n<h3 id=\"developpement\">Développement</h3>\n<p><strong>À la main</strong><br>\nUne méthode simple et efficace d'écrire du HTML, c'est idéal pour les pages super simples.</p>\n<p><strong>Générateurs de site statique</strong><br>\nLa plupart des sites Jamstack sont propulsés par un générateur de site statique.\nVous êtes libres de choisir votre GSS.</p>\n<ul>\n<li><a href=\"https://www.gatsbyjs.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Gatsby</a></li>\n<li><a href=\"https://nextjs.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Next.js</a></li>\n<li><a href=\"https://gohugo.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Hugo</a></li>\n</ul>\n<p><a href=\"https://www.staticgen.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Voir davantage de générateurs</a></p>\n<p><strong>Framework frontend</strong><br>\nLa 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>\n<ul>\n<li><a href=\"http://reactjs.org/\" target=\"_blank\" rel=\"noopener noreferrer\">React</a></li>\n<li><a href=\"https://vuejs.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Vue.js</a></li>\n<li><a href=\"https://preactjs.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Preact</a></li>\n</ul>\n<h3 id=\"deploiement\">Déploiement</h3>\n<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>\n<ul>\n<li><a href=\"https://netlify.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Netlify</a></li>\n<li><a href=\"https://vercel.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Vercel</a></li>\n<li><a href=\"https://pages.github.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Github Pages</a></li>\n</ul>\n<p><a href=\"https://www.thenewdynamic.org/tools/hosting-deployment/\" target=\"_blank\" rel=\"noopener noreferrer\">Voir plus de services de déploiement</a></p>\n<h3 id=\"parties-dynamiques\">Parties dynamiques</h3>\n<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>\n<p><strong>Fonctions personnalisées</strong><br>\nVous 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>\n<p><strong>Commentaires</strong><br>\nBeaucoup de sites Jamstack intègrent des sections pour les commentaires, principalement sur des blogs.</p>\n<p><strong>Formulaires</strong><br>\nUn excellent moyen d'interagir avec votre audience.</p>\n<p><strong>E-Commerce</strong><br>\nMettre en place une boutique en ligne sur un site Jamstack n'a jamais été aussi simple.</p>\n<p><strong>Recherche</strong><br>\nReposez-vous sur des services tiers pour intégrer des fonctionnalités de recherche.</p>\n<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>\n<h3 id=\"cms\">CMS</h3>\n<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>\n<ul>\n<li><a href=\"http://contentful.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Contentful</a></li>\n<li><a href=\"https://forestry.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Forestry</a></li>\n<li><a href=\"https://docs.ghost.org/api/content/\" target=\"_blank\" rel=\"noopener noreferrer\">Ghost</a></li>\n<li><a href=\"https://developer.wordpress.org/rest-api/\" target=\"_blank\" rel=\"noopener noreferrer\">Headless WordPress</a></li>\n<li><a href=\"https://www.netlifycms.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Netlify CMS</a></li>\n<li><a href=\"https://strapi.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Strapi</a></li>\n</ul>\n<p><a href=\"https://headlesscms.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Voir plus de services de gestion de contenu</a></p>\n<h3 id=\"ressources\">Ressources</h3>\n<p>Voici une sélection de ressources sur la Jamstack qui comporte des matériaux d'apprentissage ainsi que des listes de services.</p>\n<h4 id=\"services\">Services</h4>\n<ul>\n<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>\n<li><a href=\"https://headlesscms.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Une liste de gestionnaires de contenu pour les sites Jamstack</a></li>\n<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>\n<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>\n</ul>\n<h4 id=\"articles\">Articles</h4>\n<ul>\n<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>\n<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>\n<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>\n<li><a href=\"/2017/03/17/smashing-mag-va-dix-fois-plus-vite/\">Smashing Magazine va dix fois plus vite</a></li>\n<li><a href=\"https://blog.ghost.org/jamstack/\" target=\"_blank\" rel=\"noopener noreferrer\">Ghost avec la Jamstack</a></li>\n<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>\n</ul>\n<h4 id=\"videos\">Vidéos</h4>\n<ul>\n<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>\n<li><a href=\"https://vimeo.com/163522126\" target=\"_blank\" rel=\"noopener noreferrer\">La nouvelle stack Front-end, présentation de Mathias Biilmann</a></li>\n<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>\n<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>\n</ul>\n<h4 id=\"podcast\">Podcast</h4>\n<ul>\n<li><a href=\"https://www.heavybit.com/library/podcasts/jamstack-radio/\" target=\"_blank\" rel=\"noopener noreferrer\">Jamstack Radio</a></li>\n</ul>\n<hr>\n<h2 id=\"a-propos\">À propos</h2>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/02/06/de-wordpress-a-hugo-un-nouvel-etat-d-esprit/",
      "url": "https://jamstatic.fr/2019/02/06/de-wordpress-a-hugo-un-nouvel-etat-d-esprit/",
      "title": "De WordPress à Hugo : adopter un nouvel état d'esprit",
      "summary": "Comparons le fonctionnement de WordPress et de Hugo pour vous aider à vous familiariser avec ce nouvel environnement et vous imprégner de sa philosophie.",
      "date_published": "2019-02-06T16:12:14+00:00",
      "date_modified": "2019-05-03T14:12:14+00:00","content_text": "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.\nNous 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.\nNous allons partir de the_post(), the_loop et de la hiérarchie de modèle, pour mieux comprendre comment Hugo fonctionne !\nDe WordPress à Hugo\nVu 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.\nMoi aussi je faisais principalement du développement avec WordPress, avant de devenir complètement accro à Hugo.\nEt 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.\nJe 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.\nMais 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.\nDonc si vous débutez avec Hugo, et que vous connaissez WordPress, ce qui va suivre ne pourra que vous être bénéfique.\nTout est page\nCette affirmation catégorique est essentielle pour mieux appréhender le fonctionnement d'Hugo, surtout en ce qui concerne la logique dans les gabarits.\nPour 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.\nOn peut le voir ainsi : tout ce qui possède une URL, c'est une page !\nSi pour Hugo, tout est page, il faut néanmoins faire quelques distinctions bien nettes. Parmi elles, il y a les les Types et les Kinds.\nType\nSi dans WordPress, toute entrée est un post avec un type distinct. Un article c'est un post de type post, une page c'est post de type page et une recette, c'est un post de type personnalisé recipe (ou ce qui vous chante).\nDans Hugo, chaque entrée ou fichier de contenu est une page 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 :\n\nVous ajoutez le type désiré dans le front matter\nOu plus généralement, vous laissez le premier niveau d'arborescence de contenu définir le type du fichier.\n\nDonc pour créer une page de type recette, vous pouvez soit écrire le front matter suivant :\ntitle: De délicieux cupcakes\ntype: recette\n---\nOu vous reposer sur la structure de votre arborescence et laisser faire la magie :\ncontent\n  ├── post\n  └── recette\n      └── de-delicieux-cupcakes.md\nKind\nDans WordPress nous pouvons distinguer les layouts des templates. La page d'index qui affiche vos articles est construite d'après le fichier archive.php, cela s'appelle une archive. Et la page qui affiche le détail d'un article est elle construite à partir du fichier single.php, et s'appelle une single.\nD'où les fonctions booléennes is_single(), is_archive()!\nDans Hugo, une fois encore, tout est page. Et donc pour déterminer ce que nous sommes supposés afficher, nous allons utiliser le mot Kind.\nVoici différents exemples de valeurs pour kind :\n\nLa page d'accueil de votre site web est la seule qui ait le kind homepage\nLa page qui affiche vos recettes est une page avec le kind section\nLa page qui affiche vos recettes catégorisées dans chocolat est une page avec le kind taxonomy\nLa page qui regroupe toutes les catégories de vos recette (dont celles au chocolat) est une page avec le kind taxonomyTerm\nEnfin la page qui affiche une recette est la page la plus commune est de kind page.\n\nModèles et hiérarchie\nMaintenant que nous avons vu Type et Kind, plongeons nous dans la logique des gabarits de page d'Hugo.\nTout ce qui se trouve dans le dossier layouts, 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 l'ordre de consultation des modèles.\nEn plus des conventions sur les noms de fichier, Hugo se base aussi sur l'arborescence des dossiers pour savoir quel modèle appliquer.\nComme évoqué précédemment, WordPress se base sur le fichier archive.php pour la mise en page de la liste d'articles de blog. Hugo se base lui sur un fichier list.html pour remplir cette fonction.\nDe nombreux paramètres dont Kind, Type, 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.\nLa meilleure approche pour comprendre la logique de l'organisation des modèles avec Hugo est encore de lire la documentation officielle à ce sujet.\nLes modèles de page personnalisés\nC'est un des trucs plus anciens de WordPress.\nSi 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 :\n&lt;?php \/* Template Name: Custom 🤮 *\/ ?&gt;\nAvec 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 : layout.\nEnsuite nommez votre fichier d'après la valeur définie pour layout, placez-le dans le dossier layouts\/_default\/ et c'est bon !\n---\ntitle: À propos\nlayout: about\n---\nÀ propos de moi !\nlayouts\n  └── _default\n      └── about.html\n\nLes fichiers includes\nUne bonne pratique WordPress consiste à utiliser la fonction get_template_parts pour inclure un fichier de votre thème. Cela permet d'hériter des variables globales définies par WordPress ($post, $wp_query, etc.) mais c'est tout.\nDans Hugo, on parle de fichiers partiels. Ce sont des fichiers stockés dans layouts\/partials qui seront chargés lors de l'appel à la fonction partial.\nLe 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.\nOn inclut un fichier partiel ainsi :\n{{ partial \"post-head\" . }}\nLe point ci-dessus .............☝️ correspond à la page courante.\nLe 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.\nComprendre le contexte dans Hugo, c'est la clé. Si ce n'est pas encore clair pour vous, 👉 lisez Hugo, le point sur le contexte\nLa boucle et les données\nLes variables de Page\nDans WordPress, les données relatives à un article sont accessibles depuis les fichiers de gabarits de page via des fonctions comme the_permalink(), the_title(), the_content(), the_date() etc.\nHugo de son côté vous fourni un objet qui comprend des variables et des méthodes appelé le contexte de page et stocké dans le fameux point (.) mentionné plus tôt.\nDans Hugo, les équivalents aux expressions WordPress citées un peu plus haut sont .Permalink, .Title , .Content, .Date.\nVous 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 :\n{{\/* layouts\/partials\/post-head.html *\/}}\n&lt;div class=\"post-head\"&gt;\n  &lt;h1&gt;&lt;a href=\"{{ .Permalink }}\"&gt;{{ .Title }}&lt;\/a&gt;&lt;h1&gt;\n  &lt;time&gt;{{ .Date }}&lt;\/time&gt;\n&lt;\/div&gt;\nBoucler avec range\nPouvoir parcourir des articles pour construire des pages archives ou un widget Derniers articles est essentiel pour un moteur de template.\nSelon 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 single.\nDonc que ce soit pour le fichier des archives des posts de blog, des archives de la catégorie chocolat des recettes, ces éléments sont dans la boucle Loop, paginés.\nAvec Hugo, dans les gabarits de listes, les pages concernées sont stockées dans une collection et sont accessibles via l'objet .Pages.\nCela fait que pour le modèle de liste de la section recettes, .Pages retournera la collection des pages correspondantes : recettes.\nPour une liste de taxonomie, .Pages contiendra la liste des pages qui utilisent cette taxonomie ainsi que les informations sur la taxonomie en elle-même stockés dans .Data, telles que .Data.Singular, .Data.Plural et bien plus.\nUne chose à retenir, c'est que contrairement à WordPress, pour une page single, .Pages sera vide (arf) car toutes les informations de la page sont déjà disponibles dans le contexte de page ..\nComparaison des boucles\nVoici notre boucle WordPress tant aimée :\n\/\/ theme\/archive.php\n&lt;?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?&gt;\n  &lt;!-- post --&gt;\n  &lt;h2&gt;\n    &lt;a href=\"&lt;?php the_permalink(); ?&gt;\"&gt;&lt;?php the_title() ?&gt;&lt;\/a&gt;\n  &lt;\/h2&gt;\n  &lt;h6&gt;&lt;?php the_date(); ?&gt;&lt;\/h6&gt;\n  &lt;p&gt;\n    &lt;?php the_excerpt(); ?&gt;\n  &lt;\/p&gt;\n  &lt;hr&gt;\n&lt;?php endwhile; ?&gt;\n&lt;?php else: ?&gt;\n&lt;!-- aucun post trouvé --&gt;\n&lt;?php endif; ?&gt;\nLa même chose en beaucoup plus lisible et succinct avec Hugo, grâce à la transmission du contexte dans les fonctions :\n\/\/layouts\/_default\/list.html\n{{ with .Pages }}\n  {{ range . }}\n    &lt;h2&gt;\n      &lt;a href=\"{{ .Permalink }}\"&gt;{{ .Title }}&lt;\/a&gt;\n    &lt;\/h2&gt;\n    &lt;h6&gt;{{ .Date.Format \"January, 02 2006\" }}&lt;\/h6&gt;\n    &lt;p&gt;\n      {{ .Summary }}\n    &lt;\/p&gt;\n    &lt;hr&gt;\n  {{ end }}\n{{ else }}\n&lt;!-- aucun élément trouvé --&gt;\n{{ end }}\nQu'en-est-il des autres pages ?\nPour 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.\nAvec Hugo, nous avez simplement à appeler la collection .Site.pages. 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 posts, on utilisera .Site.RegularPages.\nVoici un exemple de requête plus avancée avec WordPress, qui permet d'afficher un widget Les dernières recettes ordonnées en fonction d'un paramètre défini, et utilisable dans n'importe quel modèle de page :\n&lt;?php\n$recents = new WP_Query(\n  [\n    'post_type'=&gt;'recipe',\n    'posts_per_page'=&gt;5,\n    'orderby'   =&gt; 'meta_value_num',\n    'meta_key'  =&gt; 'rating',\n  ]\n);\nif ( $recents-&gt;have_posts() ) : while ( $recents-&gt;have_posts() ) : $recents-&gt;the_post(); ?&gt;\n  &lt;h2&gt;\n    &lt;a href=\"&lt;?php $recents-&gt;the_permalink(); ?&gt;\"&gt;&lt;?php $recents-&gt;the_title() ?&gt;&lt;\/a&gt;\n  &lt;\/h2&gt;\n&lt;!-- post --&gt;\n&lt;?php endwhile; ?&gt;\n&lt;?php else: ?&gt;\n&lt;!-- aucune entrée trouvée --&gt;\n&lt;?php endif; ?&gt;\n?&gt;\nEt voici son élégante variante avec Hugo :\n{{ $recents := (where .Site.RegularPages \"Type\" \"recipe\").ByParam \"rating\" }}\n{{ range first 5 $recents }}\n  &lt;h2&gt;\n    &lt;a href=\"{{ .Permalink }}\"&gt;{{ .Title }}&lt;\/a&gt;\n  &lt;\/h2&gt;\n{{ end }}\nPour apprendre comment filtrer et ordonner les collections de pages dans Hugo,\nreportez-vous à la documentation de range, where et comment ordonner le contenu.\nLes shortcodes\nDans WordPress, les shortcodes sont \"des fonctions qui retournent quelque chose en sortie\", ajoutées à l'aide de plusieurs add_shortcode dans votre fichier functions.php.\nHugo supporte également les shortcodes, ils sont crées en ajoutant des modèles particuliers dans layouts\/shortcodes\/. Le contenu du fichier est récupéré avec .Inner et ses paramètres avec .Get. Contrairement à WordPress, ses derniers n'ont pas forcément besoin d'être nommés.\n\nSi le paramètre est nommé 👉 .Get \"title\".\nSinon on utilise sa position 👉 .Get 0.\n\nLes shortcodes par l'exemple\nVoici un exemple de shortcode WordPress tiré de la documentation[^1]. Il prendre en paramètre une classe, caption par défaut, et un contenu à insérer.\n&lt;?php\nfunction caption_shortcode( $atts, $content = null ) {\n  $a = shortcode_atts( array(\n    'class' =&gt; 'caption',\n  ), $atts );\n\n  return '&lt;span class=\"' . esc_attr($a['class']) . '\"&gt;' . $content . '&lt;\/span&gt;';\n}\nadd_shortcode( 'caption', 'caption_shortcode' );\nLe shortcode s'utilise ensuite ainsi :\n[caption class=\"headline\"]My Caption[\/caption]\nVoyons maintenant la réponse en une ligne d'Hugo :\n{{\/* layouts\/shortcodes\/caption.html *\/}}\n&lt;span class=\"{{ default \"caption\" (.Get 0) }}\"&gt;{{ .Inner }}&lt;\/span&gt;\nOn écrira dans son fichier Markdown :\n{{%\/* caption \"headline\" *\/%}}My Caption{{%\/* \/caption *\/%}}\nNous avons opté pour l'utilisation de la position… car il n'y a qu'un seul paramètre 🤷\nTirer parti des shortcodes d'Hugo (Julio Pescador)\nParamètres\nIl 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.\nTout comme pour l'édition de contenu, la configuration d'Hugo se fait dans des fichiers ! Et pour faire plaisir à tout le monde, vous pouvez choisir le format qui vous convient le mieux :\n\nYAML\nTOML\nJSON\n\nSi vous ne connaissez aucun d'entre eux (le dernier devrait vous dire quelques chose), vous pouvez vous pencher sur cet exemple de configuration que vous pouvez basculer entre les langues et vérifier leurs syntaxes.\nCes paramètres sont stockés dans un fichier config à la racine ou dans un répertoire dédié, qui vous permet de les grouper et d'écraser les variables d'environnement de façon plus intelligente.\nFormats de sortie\nHein, c'est quoi ça ?\nAh oui c'est vrai, WordPress ne vous a pas présenté.\nImaginons que pour chaque page vous ayez un fichier HTML cette-page\/index.html 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 AMP. Ces pages sont générées à côté de leur soeur au format HTML, respectivement cette-page\/index.json et cette-page\/index.amp.html.\nTout ce que vous avez à faire pour cela est de dire à Hugo d'ajouter les formats de sortie pour les Kinds desirés à l'aide du fichier de configuration évoqué juste avant, et d'ajouter les fichiers de templates correspondants.\nPour résumer :\n# config.yaml\noutputs:\n  homepage:\n    - HTML\n    - JSON\n  page:\n    - HTML\n    - AMP\nlayouts\n  ├── _default\n  │   └── about.html\n  ├── index.html\n  ├── index.json\n  └── recipes\n      └── single.amp.html\nEt 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 !\nJe vous conseille vivement d'aller éplicher la documentation sur les formats de sortie, grâce à eux vous pourrez ajouter une API à votre site où un fichier .ics pour vos évènements, ou qui sait ce dont vous aurez besoin sur votre prochain projet !\nBâtir une API JSON avec les formats de sortie personnalisés d'Hugo\nTraitement des assets\nWordPress ne propose rien en ce sens, c'est à vous d'utiliser votre propre gestionnaire de tâches et ses paquets de traitement des assets.\nHugo propose lui sa propre suite d'outils de traitement des assets !\n-- Heeein?\n-- Oui! Ça s'appelle les tuyaux d'Hugo et sans aucune dépendance node vous pouvez :\n\nMinifier 🗜️\nPaquetter vos fichiers 📦\nCompiler vos fichiers Sass\/Scss 👓\nAjouter une empreinte et un contrôle d'intégrité 🔑\n\nAvec quelques dépendances vous pouvez :\n\nExécuter PostCSS sur vos feuilles de style\n\nLe tout avec la même élégance à laquelle vous devez commencer à vous habituer :\n{{ $style := resources.Get \"main.scss\" | toCSS | minify | fingerprint }}\n&lt;link href=\"{{ $style.Permalink }}\" rel=\"stylesheet\"&gt;\nHugo s'assurera également que ces assets sont compilés et publiés uniquement si vous appelez leur .Permalink dans vos templates.\nTraitement des images\nLe traitement d'image par défaut de WordPress se fait uniquement lors de l'étape initiale de téléversement.\nWordPress 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.\nHugo 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 .Fit ou .Resize dans votre gabarit de page.\n{{ $img := resources.Get \"header.jpg\" | .Resize \"600x\" }}\n&lt;img src=\"{{ $img.Permalink }}\" alt=\"\"&gt;\nJe sais! Moi aussi je ne me lasse pas de ces instructions sur deux lignes !\nVous pouvez lancer des traitements d'images, soit à l'aide des tuyaux d'Hugo ou des ressources de page.\n\nLa révolution des tuyaux d'Hugo\nTraitement des images responsive avec Hugo | Laura Kalbag\nCache-bust et concatenation de fichiers JS\/SCSS avec Hugo | Ben Bozzay\nLes ressources de page d'Hugo\n\nThèmes et Plugins VS Composants de Thème\nWordPress 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.\nPour les thèmes, WordPress ne vous donne que deux niveaux de personnalisation.\nVous 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.\nSi 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.\nDans Hugo, on ne parle pas de plugins et des thèmes mais plutôt de composants.\nVous pouvez en ajouter autant que vous voulez.\nFichiers de gabarit, JavaScript, Scss, images, fichiers de données, chaînes i18n (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.\nVoyez les composants comme des thèmes enfant illimités !\nCertains 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.\nLe thème par l'exemple\nPrenons pour exemple un projet fictif de clinique dentaire, où personne ne veut avoir à trop mettre les mains dans le code. Vous aurez besoin :\n\nUn thème principal adapté pour le secteur de la santé 👩‍⚕️\nUne extension du thème principal adaptée pour le domaine dentaire 🦷\nUne extension saisonnière du thème principal 🎄\nUne solution pour gérer des menus de navigation riches\nUn ensemble de shortcodes en relation avec le médical à l'intention de l'équipe de rédaction\nUn fichier JSON pour chaque page.\n\nAvec WordPress il vous faudrait :\n\n\nThèmes\n\nUn thème santé\nUn thème dentaire enfant du thème santé\n\n\n\nPlugins\n\nUn plugin thème santé saisonnier\nUn plugin Mega Menu\nUn plugin Shordcodes médicaux\nUn plugin API REST\n\n\n\nNotez 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. 🤷‍♂️\nDans Hugo, vous n'avez qu'à ajouter ces différents répertoires dans votre dossier themes et y faire appel dans votre fichier de configuration principal.\ntheme:\n  - health-theme\n  - health-theme-dental-extension\n  - health-theme-season-extension\n  - mega-menu-component\n  - medical-shortcodes-component\n  - json-api-component\n\noutput:\n  page:\n    - HTML\n    - JSON\nL'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 page.\nC'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 layouts à la racine de votre projet (à condition que les chemins soient identiques bien entendu).\nTrucs et astuces pour développer un thème pour Hugo | Jeff McMorris\nOn en parle de l'interface du CMS ou pas ?\nOui !\nComme vous le savez, l'interface graphique n'est pas du ressort d'Hugo. Mais il existe des tonnes de solutions, dont le formidable Forestry.io qui vous permet de générer une belle interface de CMS personnalisable à partir du dépôt git de votre projet Hugo !\nCroyez-moi, elles sont toutes bien plus rapides et mieux conçues que ce bon vieux tableau de bord.\nAutres fonctionnalités notables\nAvant 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.\nMultilinguisme\nAu revoir WPML! 🥳\nHugo gère nativement le multilingue, y compris l'i18n et la traduction de chaînes de caractères.\nReportez-vous à l'article complet de Régis Philibert sur la gestion multilingue dans Hugo.\nMenus\nLes 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 Walker pas forcément évidente à lire et à comprendre lorsqu'il s'agit de s'aventurer dans des menus à plusieurs niveaux.\nLa solution proposée par Hugo pour les menus vous laisse assigner n'importe quelle page à un menu ou à une url externe.\nConcrètement, si vous avez deux menus sur votre site, vous assigner une page donnée à un menu de la sorte :\n# \/content\/a-propos.md\ntitle: À propos\nmenu:\n  main:\n    name: Qui suis-je?\n    weight: 2\n  footer:\n    weight: 1\nSi vous avez besoin d'ajouter une url externe au menu main, ça se fait au niveau de la configuration de votre site :\n# \/config.yaml\nmenus:\n  main:\n    - name: Blog\n      url: https:\/\/blog.tumblr.com\n      weight: 3\n  footer:\n    - name: Blog\n      url: https:\/\/blog.tumblr.com\nDans l'exemple ci-dessus, votre lien de navigation vers votre page À propos apparaîtra en seconde position dans votre menu principal avec l'intitulé Qui suis-je ? Il apparaîtra également dans le menu de bas de page en première place en reprenant le titre de page: À propos.\nEn plus de cela, les deux menus incluent un lien externe Blog qui pointe vers votre ancien blog tumblr.\nContrairement à WordPress, il n'y a pas de notion d'emplacement de menu.\nVous appelez votre objet où vous voulez dans votre gabarit de page avec .Site.Menus.main, .Site.Menus.footer or .Site.Menus.cequevousvoulez et vous bouclez ensuite dessus avec range.\nEncore une fois allez voir la doc pour apprendre à ajouter des menus dans vos gabarits de page, c'est une grande avancée par rapport aux bons vieux Walkers (ici c'est plus 🏃‍♀️).\nChamps personnalisés\nAvec WordPress, à moins que vous aimiez passer des heures à réinventer la roue, vous allez tout le temps vous reposer sur le plugin ACF pour récupérer et modifier les métadonnées des articles.\nHugo, comme Jekyll et d'autres générateurs basés sur Markdown, se repose sur Front Matter pour la gestion de toutes les variables \"perso\".\nCela permet de stocker tous les paramètres non réservés de votre contexte de page dans l'objet .Params.\nDonc dans votre gabarit, plutôt que :\n&lt;?php if ($subfield = get_field('subfield')){ echo $subfield; } ?&gt;\nvous écrivez :\n{{ with .Params.subtitle }}{{ . }}{{ end }}\n.................................................... ☝️ Vous allez l'aimer ce point !\nLes options génériques du site\nQu'en est-il de ces options génériques non rattachées à une page en particulier ?\nEt 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 !\nHugo vous propose deux manière de faire. Vous pouvez ajouter des variables personnalisés comme tagline à l'objet Params dans le fichier de configuration principal de votre site et les récupérer avec .Site.Params.tagline par exemple.\nSi vous avez des ensembles de données plus complexes, vous pouvez ajouter des fichiers yaml|toml|json dans votre dossier data\/. Tout ce qui s'y trouve sera agrégé dans un objet bien pratique .Site.Data accessible dans vos gabarits.\nDonc 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 data.\n# data\/socials.yaml\n- title: Facebook\n  icon: fb\n  url: https:\/\/facebook.com\/hugo_rocks\n- title: Twitter\n  icon: tw\n  url: https:\/\/twitter.com\/hugoRocks\n# data\/options.yaml\nsocials: true\ntagline: Hugo rocks!\nEt dans votre fichier partiel…\n{{\/* layouts\/partials\/socials.html *\/}}\n{{ if .Site.Data.options.social }}\n&lt;ul class=\"socials\"&gt;\n  {{ range .Site.Data.socials }}\n    &lt;li&gt;\n      &lt;a href=\"{{ .url }}\"&gt;&lt;i class=\"icon icon-{{ .icon }}\"&gt;&lt;\/i&gt; {{ .title }}&lt;\/a&gt;\n    &lt;\/li&gt;\n  {{ end }}\n&lt;\/ul&gt;\n{{ end }}\nUtilisation des fichiers de données dans Hugo par l'exemple | Peter Y. Chuang\nCommentaires\nJe 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.\nComme 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.\nHeureusement il existe un support natif de Disqus prêt à l'emploi.\nEt 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.\n\nRemplacer Disqus avec les commentaires Github | Don Williamson\nHugo + Staticman : réponses imbriquées et notifications par email | Dan C Williams\n\nFormulaires\nLà aussi, il faut passer par un service tiers, le plus souvent gratuit, par exemple :\n\nFormkeep.io\nNetlify's forms\nTypeForm\n\nContenu relatif\nGé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.\nHugo 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 relation de contenus\nRecherche\nComme 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 :\n\nLunr.js 🆓\nAlgolia et leur incroyable widget InstantSearch.js (🆓 pour les sites de petite et moyenne tailles)\n\n\nRecherche sur Bleve avec Hugo\nRecherche côté client pour Hugo avec Fuse.js (Eddie Webb)\n\nConclusion\nCet 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.\nNous 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 ! 🏃",
      "content_html": "<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>\n<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>\n<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>\n<h2 id=\"de-wordpress-a-hugo\">De WordPress à Hugo</h2>\n<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.\nMoi aussi je faisais principalement du développement avec WordPress, avant de devenir complètement accro à Hugo.</p>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"tout-est-page\">Tout est page</h2>\n<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>\n<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>\n<p>On peut le voir ainsi : tout ce qui possède une URL, c'est une page !</p>\n<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>\n<h3 id=\"type\">Type</h3>\n<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>\n<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>\n<ol>\n<li>Vous ajoutez le <code>type</code> désiré dans le front matter</li>\n<li>Ou plus généralement, vous laissez le premier niveau d'arborescence de contenu définir le type du fichier.</li>\n</ol>\n<p>Donc pour créer une page de type recette, vous pouvez soit écrire le front matter suivant :</p>\n<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>\n<span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">recette</span>\n<span class=\"hljs-meta\">---</span></code></pre>\n<p>Ou vous reposer sur la structure de votre arborescence et laisser faire la magie :</p>\n<pre><code class=\"language-txt\">content\n  ├── post\n  └── recette\n      └── de-delicieux-cupcakes.md</code></pre>\n<h3 id=\"kind\">Kind</h3>\n<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>\n<p>D'où les fonctions booléennes <code>is_single()</code>, <code>is_archive()</code>!</p>\n<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>\n<p>Voici différents exemples de valeurs pour <code>kind</code> :</p>\n<ul>\n<li>La page d'accueil de votre site web est la seule qui ait le <code>kind</code> <code>homepage</code></li>\n<li>La page qui affiche vos recettes est une page avec le <code>kind</code> <code>section</code></li>\n<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>\n<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>\n<li>Enfin la page qui affiche une recette est la page la plus commune est de <code>kind</code> <code>page</code>.</li>\n</ul>\n<h2 id=\"modeles-et-hierarchie\">Modèles et hiérarchie</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h3 id=\"les-modeles-de-page-personnalises\">Les modèles de page personnalisés</h3>\n<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>\n<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>\n<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>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">À</span> <span class=\"hljs-string\">propos</span>\n<span class=\"hljs-attr\">layout:</span> <span class=\"hljs-string\">about</span>\n<span class=\"hljs-meta\">---</span>\n<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>\n<pre><code class=\"language-txt\">layouts\n  └── _default\n      └── about.html\n</code></pre>\n<h3 id=\"les-fichiers-includes\">Les fichiers includes</h3>\n<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>\n<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>\n<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>\n<p>On inclut un fichier partiel ainsi :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"post-head\"</span> . }}</code></pre>\n<p>Le point ci-dessus .............☝️ correspond à la page courante.</p>\n<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>\n<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>\n<h2 id=\"la-boucle-et-les-donnees\">La boucle et les données</h2>\n<h3 id=\"les-variables-de-page\">Les variables de Page</h3>\n<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>\n<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>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{<span class=\"hljs-comment\">/* layouts/partials/post-head.html */</span>}}\n&lt;div class=<span class=\"hljs-string\">\"post-head\"</span>&gt;\n  &lt;h1&gt;&lt;a href=<span class=\"hljs-string\">\"{{ .Permalink }}\"</span>&gt;{{ .Title }}&lt;/a&gt;&lt;h1&gt;\n  &lt;time&gt;{{ .Date }}&lt;/time&gt;\n&lt;/div&gt;</code></pre>\n<h3 id=\"boucler-avec-range\">Boucler avec <code>range</code></h3>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h3 id=\"comparaison-des-boucles\">Comparaison des boucles</h3>\n<p>Voici notre boucle WordPress tant aimée :</p>\n<pre><code class=\"language-php hljs php\"><span class=\"hljs-comment\">// theme/archive.php</span>\n<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>\n  &lt;!-- post --&gt;\n  &lt;h2&gt;\n    &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;\n  &lt;/h2&gt;\n  &lt;h6&gt;<span class=\"hljs-meta\">&lt;?php</span> the_date(); <span class=\"hljs-meta\">?&gt;</span>&lt;/h6&gt;\n  &lt;p&gt;\n    <span class=\"hljs-meta\">&lt;?php</span> the_excerpt(); <span class=\"hljs-meta\">?&gt;</span>\n  &lt;/p&gt;\n  &lt;hr&gt;\n<span class=\"hljs-meta\">&lt;?php</span> <span class=\"hljs-keyword\">endwhile</span>; <span class=\"hljs-meta\">?&gt;</span>\n<span class=\"hljs-meta\">&lt;?php</span> <span class=\"hljs-keyword\">else</span>: <span class=\"hljs-meta\">?&gt;</span>\n&lt;!-- aucun post trouvé --&gt;\n<span class=\"hljs-meta\">&lt;?php</span> <span class=\"hljs-keyword\">endif</span>; <span class=\"hljs-meta\">?&gt;</span></code></pre>\n<p>La même chose en beaucoup plus lisible et succinct avec Hugo, grâce à la transmission du contexte dans les fonctions :</p>\n<pre><code class=\"language-go-html-template hljs go\"><span class=\"hljs-comment\">//layouts/_default/list.html</span>\n{{ with .Pages }}\n  {{ <span class=\"hljs-keyword\">range</span> . }}\n    &lt;h2&gt;\n      &lt;a href=<span class=\"hljs-string\">\"{{ .Permalink }}\"</span>&gt;{{ .Title }}&lt;/a&gt;\n    &lt;/h2&gt;\n    &lt;h6&gt;{{ .Date.Format <span class=\"hljs-string\">\"January, 02 2006\"</span> }}&lt;/h6&gt;\n    &lt;p&gt;\n      {{ .Summary }}\n    &lt;/p&gt;\n    &lt;hr&gt;\n  {{ end }}\n{{ <span class=\"hljs-keyword\">else</span> }}\n&lt;!-- aucun élément trouvé --&gt;\n{{ end }}</code></pre>\n<h3 id=\"qu-en-est-il-des-autres-pages聽\">Qu'en-est-il des autres pages ?</h3>\n<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>\n<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>\n<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>\n<pre><code class=\"language-php hljs php\"><span class=\"hljs-meta\">&lt;?php</span>\n$recents = <span class=\"hljs-keyword\">new</span> WP_Query(\n  [\n    <span class=\"hljs-string\">'post_type'</span>=&gt;<span class=\"hljs-string\">'recipe'</span>,\n    <span class=\"hljs-string\">'posts_per_page'</span>=&gt;<span class=\"hljs-number\">5</span>,\n    <span class=\"hljs-string\">'orderby'</span>   =&gt; <span class=\"hljs-string\">'meta_value_num'</span>,\n    <span class=\"hljs-string\">'meta_key'</span>  =&gt; <span class=\"hljs-string\">'rating'</span>,\n  ]\n);\n<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>\n  &lt;h2&gt;\n    &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;\n  &lt;/h2&gt;\n&lt;!-- post --&gt;\n<span class=\"hljs-meta\">&lt;?php</span> <span class=\"hljs-keyword\">endwhile</span>; <span class=\"hljs-meta\">?&gt;</span>\n<span class=\"hljs-meta\">&lt;?php</span> <span class=\"hljs-keyword\">else</span>: <span class=\"hljs-meta\">?&gt;</span>\n&lt;!-- aucune entrée trouvée --&gt;\n<span class=\"hljs-meta\">&lt;?php</span> <span class=\"hljs-keyword\">endif</span>; <span class=\"hljs-meta\">?&gt;</span>\n<span class=\"hljs-meta\">?&gt;</span></code></pre>\n<p>Et voici son élégante variante avec Hugo :</p>\n<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> }}\n{{ <span class=\"hljs-keyword\">range</span> first <span class=\"hljs-number\">5</span> $recents }}\n  &lt;h2&gt;\n    &lt;a href=<span class=\"hljs-string\">\"{{ .Permalink }}\"</span>&gt;{{ .Title }}&lt;/a&gt;\n  &lt;/h2&gt;\n{{ end }}</code></pre>\n<aside class=\"note\"><p>Pour apprendre comment filtrer et ordonner les collections de pages dans Hugo,\nreportez-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>\n<h2 id=\"les-shortcodes\">Les shortcodes</h2>\n<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>\n<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>\n<ul>\n<li>Si le paramètre est nommé 👉 <code>.Get \"title\"</code>.</li>\n<li>Sinon on utilise sa position 👉 <code>.Get 0</code>.</li>\n</ul>\n<h3 id=\"les-shortcodes-par-l-exemple\">Les shortcodes par l'exemple</h3>\n<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>\n<pre><code class=\"language-php hljs php\"><span class=\"hljs-meta\">&lt;?php</span>\n<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>{\n  $a = shortcode_atts( <span class=\"hljs-keyword\">array</span>(\n    <span class=\"hljs-string\">'class'</span> =&gt; <span class=\"hljs-string\">'caption'</span>,\n  ), $atts );\n\n  <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>;\n}\nadd_shortcode( <span class=\"hljs-string\">'caption'</span>, <span class=\"hljs-string\">'caption_shortcode'</span> );</code></pre>\n<p>Le shortcode s'utilise ensuite ainsi :</p>\n<pre><code class=\"language-html hljs xml\">[caption class=\"headline\"]My Caption[/caption]</code></pre>\n<p>Voyons maintenant la réponse en une ligne d'Hugo :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{<span class=\"hljs-comment\">/* layouts/shortcodes/caption.html */</span>}}\n&lt;span class=<span class=\"hljs-string\">\"{{ default \"</span>caption<span class=\"hljs-string\">\" (.Get 0) }}\"</span>&gt;{{ .Inner }}&lt;/span&gt;</code></pre>\n<p>On écrira dans son fichier Markdown :</p>\n<pre><code class=\"language-md hljs markdown\">{{%/<span class=\"hljs-emphasis\">* caption \"headline\" *</span>/%}}My Caption{{%/<span class=\"hljs-emphasis\">* /caption *</span>/%}}</code></pre>\n<p>Nous avons opté pour l'utilisation de la <em>position</em>… car il n'y a qu'un seul paramètre 🤷</p>\n<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>\n<h2 id=\"parametres\">Paramètres</h2>\n<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>\n<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>\n<ul>\n<li>YAML</li>\n<li>TOML</li>\n<li>JSON</li>\n</ul>\n<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>\n<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>\n<h2 id=\"formats-de-sortie\">Formats de sortie</h2>\n<p>Hein, c'est quoi ça ?</p>\n<p>Ah oui c'est vrai, WordPress ne vous a pas présenté.</p>\n<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>\n<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>\n<p>Pour résumer :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># config.yaml</span>\n<span class=\"hljs-attr\">outputs:</span>\n  <span class=\"hljs-attr\">homepage:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">HTML</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">JSON</span>\n  <span class=\"hljs-attr\">page:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">HTML</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">AMP</span></code></pre>\n<pre><code class=\"language-sh hljs bash\">layouts\n  ├── _default\n  │   └── about.html\n  ├── index.html\n  ├── index.json\n  └── recipes\n      └── single.amp.html</code></pre>\n<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>\n<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>\n<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>\n<h2 id=\"traitement-des-assets\">Traitement des assets</h2>\n<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>\n<p>Hugo propose lui sa propre suite d'outils de traitement des assets !</p>\n<p>-- Heeein?\n-- 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>\n<ul>\n<li>Minifier 🗜️</li>\n<li>Paquetter vos fichiers 📦</li>\n<li>Compiler vos fichiers <a href=\"http://sass-lang.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Sass/Scss</a> 👓</li>\n<li>Ajouter une empreinte et un contrôle d'intégrité 🔑</li>\n</ul>\n<p>Avec quelques dépendances vous pouvez :</p>\n<ul>\n<li>Exécuter <a href=\"https://postcss.org/\" target=\"_blank\" rel=\"noopener noreferrer\">PostCSS</a> sur vos feuilles de style</li>\n</ul>\n<p>Le tout avec la même élégance à laquelle vous devez commencer à vous habituer :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ $style := resources.Get <span class=\"hljs-string\">\"main.scss\"</span> | toCSS | minify | fingerprint }}\n&lt;link href=<span class=\"hljs-string\">\"{{ $style.Permalink }}\"</span> rel=<span class=\"hljs-string\">\"stylesheet\"</span>&gt;</code></pre>\n<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>\n<h3 id=\"traitement-des-images\">Traitement des images</h3>\n<p>Le traitement d'image par défaut de WordPress se fait uniquement lors de l'étape initiale de téléversement.</p>\n<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>\n<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>\n<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> }}\n&lt;img src=<span class=\"hljs-string\">\"{{ $img.Permalink }}\"</span> alt=<span class=\"hljs-string\">\"\"</span>&gt;</code></pre>\n<p>Je sais! Moi aussi je ne me lasse pas de ces instructions sur deux lignes !</p>\n<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>\n<aside class=\"note\"><ul>\n<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>\n<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>\n<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>\n<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>\n</ul></aside>\n<h2 id=\"themes-et-plugins-vs-composants-de-theme\">Thèmes et Plugins VS Composants de Thème</h2>\n<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>\n<p>Pour les thèmes, WordPress ne vous donne que deux niveaux de personnalisation.\nVous 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>\n<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>\n<p>Dans Hugo, on ne parle pas de plugins et des thèmes mais plutôt de composants.\nVous pouvez en ajouter autant que vous voulez.</p>\n<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>\n<p>Voyez les composants comme des thèmes enfant illimités !</p>\n<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>\n<h3 id=\"le-theme-par-l-exemple\">Le thème par l'exemple</h3>\n<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>\n<ul>\n<li>Un thème principal adapté pour le secteur de la santé 👩‍⚕️</li>\n<li>Une extension du thème principal adaptée pour le domaine dentaire 🦷</li>\n<li>Une extension saisonnière du thème principal 🎄</li>\n<li>Une solution pour gérer des menus de navigation riches</li>\n<li>Un ensemble de shortcodes en relation avec le médical à l'intention de l'équipe de rédaction</li>\n<li>Un fichier JSON pour chaque page.</li>\n</ul>\n<p>Avec WordPress il vous faudrait :</p>\n<ul>\n<li>\n<p>Thèmes</p>\n<ul>\n<li>Un thème santé</li>\n<li>Un thème dentaire enfant du thème santé</li>\n</ul>\n</li>\n<li>\n<p>Plugins</p>\n<ul>\n<li>Un plugin thème santé saisonnier</li>\n<li>Un plugin Mega Menu</li>\n<li>Un plugin Shordcodes médicaux</li>\n<li>Un plugin API REST</li>\n</ul>\n</li>\n</ul>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">theme:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">health-theme</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">health-theme-dental-extension</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">health-theme-season-extension</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">mega-menu-component</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">medical-shortcodes-component</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">json-api-component</span>\n\n<span class=\"hljs-attr\">output:</span>\n  <span class=\"hljs-attr\">page:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">HTML</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">JSON</span></code></pre>\n<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>\n<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>\n<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>\n<h2 id=\"on-en-parle-de-l-interface-du-cms-ou-pas\">On en parle de l'interface du CMS ou pas ?</h2>\n<p>Oui !</p>\n<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>\n<p>Croyez-moi, elles sont toutes bien plus rapides et mieux conçues que ce bon vieux tableau de bord.</p>\n<h2 id=\"autres-fonctionnalites-notables\">Autres fonctionnalités notables</h2>\n<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>\n<h3 id=\"multilinguisme\">Multilinguisme</h3>\n<p>Au revoir WPML! 🥳</p>\n<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>\n<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>\n<h3 id=\"menus\">Menus</h3>\n<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>\n<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>\n<p>Concrètement, si vous avez deux menus sur votre site, vous assigner une page donnée à un menu de la sorte :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># /content/a-propos.md</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">À</span> <span class=\"hljs-string\">propos</span>\n<span class=\"hljs-attr\">menu:</span>\n  <span class=\"hljs-attr\">main:</span>\n    <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Qui</span> <span class=\"hljs-string\">suis-je?</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">2</span>\n  <span class=\"hljs-attr\">footer:</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">1</span></code></pre>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># /config.yaml</span>\n<span class=\"hljs-attr\">menus:</span>\n  <span class=\"hljs-attr\">main:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Blog</span>\n      <span class=\"hljs-attr\">url:</span> <span class=\"hljs-string\">https://blog.tumblr.com</span>\n      <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">3</span>\n  <span class=\"hljs-attr\">footer:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Blog</span>\n      <span class=\"hljs-attr\">url:</span> <span class=\"hljs-string\">https://blog.tumblr.com</span></code></pre>\n<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>.\nEn plus de cela, les deux menus incluent un lien externe <em>Blog</em> qui pointe vers votre ancien blog tumblr.</p>\n<p>Contrairement à WordPress, il n'y a pas de notion d'emplacement de menu.\nVous 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>\n<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>\n<h3 id=\"champs-personnalises\">Champs personnalisés</h3>\n<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>\n<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\".\nCela permet de stocker tous les paramètres non réservés de votre contexte de page dans l'objet <code>.Params</code>.</p>\n<p>Donc dans votre gabarit, plutôt que :</p>\n<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>\n<p>vous écrivez :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ with .Params.subtitle }}{{ . }}{{ end }}</code></pre>\n<p>.................................................... ☝️ Vous allez l'aimer ce point !</p>\n<h3 id=\"les-options-generiques-du-site\">Les options génériques du site</h3>\n<p>Qu'en est-il de ces options génériques non rattachées à une page en particulier ?</p>\n<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>\n<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>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># data/socials.yaml</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Facebook</span>\n  <span class=\"hljs-attr\">icon:</span> <span class=\"hljs-string\">fb</span>\n  <span class=\"hljs-attr\">url:</span> <span class=\"hljs-string\">https://facebook.com/hugo_rocks</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Twitter</span>\n  <span class=\"hljs-attr\">icon:</span> <span class=\"hljs-string\">tw</span>\n  <span class=\"hljs-attr\">url:</span> <span class=\"hljs-string\">https://twitter.com/hugoRocks</span></code></pre>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># data/options.yaml</span>\n<span class=\"hljs-attr\">socials:</span> <span class=\"hljs-literal\">true</span>\n<span class=\"hljs-attr\">tagline:</span> <span class=\"hljs-string\">Hugo</span> <span class=\"hljs-string\">rocks!</span></code></pre>\n<p>Et dans votre fichier partiel…</p>\n<pre><code class=\"language-go-html-template hljs go\">{{<span class=\"hljs-comment\">/* layouts/partials/socials.html */</span>}}\n{{ <span class=\"hljs-keyword\">if</span> .Site.Data.options.social }}\n&lt;ul class=<span class=\"hljs-string\">\"socials\"</span>&gt;\n  {{ <span class=\"hljs-keyword\">range</span> .Site.Data.socials }}\n    &lt;li&gt;\n      &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;\n    &lt;/li&gt;\n  {{ end }}\n&lt;/ul&gt;\n{{ end }}</code></pre>\n<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>\n<h3 id=\"commentaires\">Commentaires</h3>\n<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>\n<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>\n<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>\n<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>\n<aside class=\"note\"><ul>\n<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>\n<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>\n</ul></aside>\n<h3 id=\"formulaires\">Formulaires</h3>\n<p>Là aussi, il faut passer par un service tiers, le plus souvent gratuit, par exemple :</p>\n<ul>\n<li>Formkeep.io</li>\n<li>Netlify's forms</li>\n<li>TypeForm</li>\n</ul>\n<h3 id=\"contenu-relatif\">Contenu relatif</h3>\n<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>\n<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>\n<h3 id=\"recherche\">Recherche</h3>\n<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>\n<ul>\n<li><a href=\"https://github.com/olivernn/lunr.js\" target=\"_blank\" rel=\"noopener noreferrer\">Lunr.js</a> 🆓</li>\n<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>\n</ul>\n<aside class=\"note\"><ul>\n<li><a href=\"http://blevesearch.com/news/Site-Search/\" target=\"_blank\" rel=\"noopener noreferrer\">Recherche sur Bleve avec Hugo</a></li>\n<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>\n</ul></aside>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>\n<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>",
      "authors": [
        {
          "name": "regis"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/02/05/implementer-les-categories-dans-eleventy/",
      "url": "https://jamstatic.fr/2019/02/05/implementer-les-categories-dans-eleventy/",
      "title": "Implémenter les catégories dans Eleventy",
      "summary": "Comment créer une collection afin de pouvoir travailler avec des catégories dans le générateur Eleventy.",
      "date_published": "2019-02-05T08:15:06+00:00",
      "date_modified": "2019-02-06T08:10:26+00:00","content_text": "Je veux disposer de catégories dans Eleventy afin de pouvoir dispatcher\nmes articles dans divers domaines génériques 1. L'idée c'est que tous les\narticles sur les livres aillent dans la catégorie \"Culture\", les articles\ntechniques dans la catégorie \"Tech\", etc.\n\nUn article n'a pas besoin de préciser une catégorie.\nUn article ne peut appartenir qu'à une seule catégorie\nPar convention, les noms de catégories commencent par une majuscule.\n\nLes tags, eux, peuvent figurer dans n'importe quelle catégorie. Un livre\nétrange et un article technique fantaisiste appartiennent à des\ncatégories différentes, mais peuvent très bien être tous les deux étiquettés \"bizarre\".\nComment ça marche à l'usage ?\nRegardons comment nous allons utiliser les catégories :\n\nComment préciser la catégorie d'un article,\nComment accéder à la catégorie d'un article dans un modèle\nComment manipuler tous les articles d'une même catégorie\n\nPréciser la catégorie\nOn utilise la propriété category ainsi pour préciser la catégorie à laquelle\nappartient l'article :\n---\ndate: 10\/30\/2018\ntitle: Loomings\ncategory: Tech\ntags:\n  - tools\n  - git\n  - eleventy\n---\n\nAppeler la catégorie dans un modèle\nDans un modèle, on fait référence à la propriété catégorie comme d'habitude :\n&lt;a href=\"\/categories\/{{category}}\"&gt;{{ category }}&lt;\/a&gt;\nManipuler les articles d'une même catégorie\nOn peut manipuler les articles d'une même catégories en créant une collection categories 2.\nPour lister tous les articles rangés dans la catégorie Tech, on pourrait procéder de la sorte :\n&lt;ul&gt;\n  {%- for article in collections.categories[\"Tech\"] -%}\n  &lt;li&gt;{{ article.data.title }}&lt;\/li&gt;\n  {%- endfor -%}\n&lt;\/ul&gt;\nTout comme l'objet collections possède une propriété pour chaque tag, l'objet\ncollections.categories possède une propriété pour chaque catégorie.\nChaque propriété fait référence à un tableau d'articles. Ça donne quelque chose comme ça :\ncollections: {\n  all: [ items ],\n  categories: {\n    Culture: [ items ],\n    Life: [ items ],\n    Thinking: [ items ]\n  }\n}\nImplémentation\nNous voulons :\n\nune liste de catégories\nun objet qui contient une propriété pour chaque catégorie, chaque propriété est une liste d'articles pour cette catégorie\n\nCréer une liste de catégories\nPour générer une liste de catégories nous itérons sur tous les fichiers générés.\nCette fonction crée une collection categoryList qui contient les noms de toutes les catégories.\ngetCatList = function (collection) {\n  let catSet = new Set();\n\n  collection\n    .getAllSorted()\n    .forEach(\n      (item) =&gt;\n        typeof item.data.category === \"string\" &amp;&amp; catSet.add(item.data.category)\n    );\n\n  return [...catSet];\n};\n\neleventyConfig.addCollection(\"categoryList\", getCatList);\nCréer une liste d'articles pour chaque catégorie\nPour 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 :\ncategories {\n  Culture: [article_1, article_4],\n  Tech: [article_3],\n  Life: [article_1, article_3]\n}\nNous pouvons utiliser la fonction makeCategories() comme callback de addCollection() pour créer cet objet. Nous itérons sur chaque élément qui possède une propriété category dans son front matter et nous l'ajoutons à la liste de cette catégorie 3 :\nmakeCategories = function (collection) {\n  let categories = {};\n\n  \/\/ Every rendered page\n\n  collection.getAllSorted().forEach((item) =&gt; {\n    let category = item.data.category;\n\n    \/\/ Ignore the ones without a category\n\n    if (typeof category !== \"string\") return;\n\n    if (Array.isArray(categories[category]))\n      \/\/  category array exists? Just push\n      categories[category].push(item);\n    \/\/  Otherwise create it and\n    \/\/  make `item` the first, uh, item.\n    else categories[category] = [item];\n  });\n\n  return categories;\n};\nPuisque nous souhaitons appeler notre collection de catégories catgories, nous la créons comme cela :\naddCollection(\"categories\", makeCategories);\nNous 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.\n\n\n\n\nÇa c'est ce que je dis maintenant. Ma première motivation était de comprendre comment marchent les collections.]&#160;&#8617;\n\n\nVous l'appelez comme vous voulez. Il se trouve que j'aime bien \"categories\".&#160;&#8617;\n\n\nCe if (Array.isArray(categories[category])) 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;&#8617;\n\n\n",
      "content_html": "<p>Je veux disposer de catégories dans Eleventy afin de pouvoir dispatcher\nmes 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\narticles sur les livres aillent dans la catégorie \"Culture\", les articles\ntechniques dans la catégorie \"Tech\", etc.</p>\n<ul>\n<li>Un article n'a pas besoin de préciser une catégorie.</li>\n<li>Un article ne peut appartenir qu'à une seule catégorie</li>\n<li>Par convention, les noms de catégories commencent par une majuscule.</li>\n</ul>\n<p>Les tags, eux, peuvent figurer dans n'importe quelle catégorie. Un livre\nétrange et un article technique fantaisiste appartiennent à des\ncatégories différentes, mais peuvent très bien être tous les deux étiquettés \"bizarre\".</p>\n<h2 id=\"comment-ca-marche-a-l-usage\">Comment ça marche à l'usage ?</h2>\n<p>Regardons comment nous allons utiliser les catégories :</p>\n<ul>\n<li>Comment préciser la catégorie d'un article,</li>\n<li>Comment accéder à la catégorie d'un article dans un modèle</li>\n<li>Comment manipuler tous les articles d'une même catégorie</li>\n</ul>\n<h3 id=\"preciser-la-categorie\">Préciser la catégorie</h3>\n<p>On utilise la propriété <code>category</code> ainsi pour préciser la catégorie à laquelle\nappartient l'article :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">date:</span> <span class=\"hljs-number\">10</span><span class=\"hljs-string\">/30/2018</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Loomings</span>\n<span class=\"hljs-attr\">category:</span> <span class=\"hljs-string\">Tech</span>\n<span class=\"hljs-attr\">tags:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">tools</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">git</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">eleventy</span>\n<span class=\"hljs-meta\">---</span>\n</code></pre>\n<h3 id=\"appeler-la-categorie-dans-un-modele\">Appeler la catégorie dans un modèle</h3>\n<p>Dans un modèle, on fait référence à la propriété catégorie comme d'habitude :</p>\n<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>\n<h3 id=\"manipuler-les-articles-d-une-meme-categorie\">Manipuler les articles d'une même catégorie</h3>\n<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>.\nPour lister tous les articles rangés dans la catégorie <code>Tech</code>, on pourrait procéder de la sorte :</p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n  </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\">\n  <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>\n  </span><span class=\"hljs-template-tag\">{%- <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> -%}</span><span class=\"xml\">\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span></span></code></pre>\n<p>Tout comme l'objet <code>collections</code> possède une propriété pour chaque tag, l'objet\n<code>collections.categories</code> possède une propriété pour chaque catégorie.\nChaque propriété fait référence à un tableau d'articles. Ça donne quelque chose comme ça :</p>\n<pre><code class=\"language-json hljs json\">collections: {\n  all: [ items ],\n  categories: {\n    Culture: [ items ],\n    Life: [ items ],\n    Thinking: [ items ]\n  }\n}</code></pre>\n<h2 id=\"implementation\">Implémentation</h2>\n<p>Nous voulons :</p>\n<ul>\n<li>une liste de catégories</li>\n<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>\n</ul>\n<h3 id=\"creer-une-liste-de-categories\">Créer une liste de catégories</h3>\n<p>Pour générer une liste de catégories nous itérons sur tous les fichiers générés.\nCette fonction crée une collection <code>categoryList</code> qui contient les noms de toutes les catégories.</p>\n<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>{\n  <span class=\"hljs-keyword\">let</span> catSet = <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Set</span>();\n\n  collection\n    .getAllSorted()\n    .forEach(\n      <span class=\"hljs-function\">(<span class=\"hljs-params\">item</span>) =&gt;</span>\n        <span class=\"hljs-keyword\">typeof</span> item.data.category === <span class=\"hljs-string\">\"string\"</span> &amp;&amp; catSet.add(item.data.category)\n    );\n\n  <span class=\"hljs-keyword\">return</span> [...catSet];\n};\n\neleventyConfig.addCollection(<span class=\"hljs-string\">\"categoryList\"</span>, getCatList);</code></pre>\n<h3 id=\"creer-une-liste-d-articles-pour-chaque-categorie\">Créer une liste d'articles pour chaque catégorie</h3>\n<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>\n<pre><code class=\"language-json hljs json\">categories {\n  Culture: [article_1, article_4],\n  Tech: [article_3],\n  Life: [article_1, article_3]\n}</code></pre>\n<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>\n<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>{\n  <span class=\"hljs-keyword\">let</span> categories = {};\n\n  <span class=\"hljs-comment\">// Every rendered page</span>\n\n  collection.getAllSorted().forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">item</span>) =&gt;</span> {\n    <span class=\"hljs-keyword\">let</span> category = item.data.category;\n\n    <span class=\"hljs-comment\">// Ignore the ones without a category</span>\n\n    <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>;\n\n    <span class=\"hljs-keyword\">if</span> (<span class=\"hljs-built_in\">Array</span>.isArray(categories[category]))\n      <span class=\"hljs-comment\">//  category array exists? Just push</span>\n      categories[category].push(item);\n    <span class=\"hljs-comment\">//  Otherwise create it and</span>\n    <span class=\"hljs-comment\">//  make `item` the first, uh, item.</span>\n    <span class=\"hljs-keyword\">else</span> categories[category] = [item];\n  });\n\n  <span class=\"hljs-keyword\">return</span> categories;\n};</code></pre>\n<p>Puisque nous souhaitons appeler notre collection de catégories <code>catgories</code>, nous la créons comme cela :</p>\n<pre><code class=\"language-js hljs javascript\">addCollection(<span class=\"hljs-string\">\"categories\"</span>, makeCategories);</code></pre>\n<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>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n<li id=\"fn:2\">\n<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>\n</li>\n<li id=\"fn:explication\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/01/29/les-collections-dans-eleventy/",
      "url": "https://jamstatic.fr/2019/01/29/les-collections-dans-eleventy/",
      "title": "Les collections dans Eleventy",
      "summary": "Les deux manières de créer des collections de documents avec le générateur de site statique Eleventy.",
      "date_published": "2019-01-29T07:54:22+00:00","content_text": "Le générateur de site statique open source Eleventy 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 la manière dont vous pouvez créer des collections de documents (appelées sections de contenu dans Hugo), Eleventy lui vous laisse le choix.\nDans Eleventy les collections permettent de grouper des articles selon divers\ncritères. Une collection pourrait désigner une série d'articles. Un autre\ncollection pourrait regrouper les articles à propos de livres. Une troisième\ncollection pourrait rassembler tous les contenus d'un même répertoire.\nEleventy vous permet de créer des collections de deux manières :\n\nimplicitement, à l'aide de tags dans le front matter\nexplicitement, avec la fonction addCollection()\n\nLes collections à base de tags\nToutes les pages qui partagent un même tag appartiennent à la même collection.\nUn modèle avec le front matter suivant va générer des pages dans les collections\nbooks et reviews.\n---\ntitle: Finding Oz\ncategory: Culture\ntags:\n  - books\n  - reviews\n---\n. . .\nDans un modèle, on accède aux collections par leur nom, en tant que propriété de\nl'object global collections.\n&lt;p&gt;\n  Le titre de cette page est :\n  {{ collections.books[0].data.title }}\n&lt;\/p&gt;\nOn utilise généralement les collections dans des boucles afin d'itérer sur\nchaque élément de la collection.\n{% for post in collections.books %}\n  {{ post.data.title }}\n  {{ post.url }}\n  {{ post.data.category }}\n  {{ post.data.tags }}\n  {{ post.date }}\n{% endfor %}\nL'objet collections lui, ressemble à ça :\n{\n  \"all\": [...],\n  \"nav\": [...],\n  \"books\": [\n    {\n      \"inputPath\": \".\/src\/articles\/finding-oz.md\",\n      \"outputPath\": \"_site\/articles\/finding-oz\/index.html\",\n      \"fileSlug\": \"finding-oz\",\n      \"data\": {...},\n      \"date\": \"2009-08-07T13:52:12.000Z\",\n      \"url\": \"\/articles\/finding-oz\/\",\n      \"templateContent\": \"&lt;p&gt;As with most books ... much about The Wizard of Oz&lt;\/li&gt;\\n&lt;\/ul&gt;\\n\",\n      \"template\": {...}\n    },\n    ...\n  ],\n  \"programming\": [...],\n}\nChaque propriété est un tableau d'objets d'éléments de\ncollection\n(également appelés objets\nmodèle dans la\ndocumentation).\nLa collection spéciale all représente un tableau de tous les objets page\ngénérés par Eleventy.\n\n\n\nPropriété\nDescription\n\n\n\n\ninputPath\nChemin vers ce fichier incluant le répertoire source. .\/src\/articles\/finding-oz.md\n\n\noutputPath\nChemin du fichier généré. articles\/finding-oz\/index.html\n\n\nfileSlug\nVersion courte en fonction du nom et de l'emplacement du fichier. En fonction des règles. finding-oz\n\n\ndata\nDonnées du front matter de la page rendue. Les variables globales disponibles pour chaque page.\n\n\ndate\nLa date du fichier au format UTC. Voir les règles. 2019-01-27T13:52:12.000Z\n\n\nurl\nChemin vers le contenu. N'inclus pas le protocole et le nom d'hôte. \/articles\/finding-oz\/\n\n\ntemplateContent\nLe contenu généré de la page, n'inclut pas les balises enveloppantes de mise en page.&lt;p&gt;Comme la plupart des livres ... à propos du Magicien d'Oz&lt;\/li&gt;\\n&lt;\/ul&gt;\\n\n\n\ntemplate\nToutes 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.\n\n\n\nImplémentation : Comment un tag devient une collection\ngetTaggedCollectionsData() est la fonction qui transforme des tags en collections.\nasync getTaggedCollectionsData() {\nlet collections = {};\ncollections.all = this.createTemplateMapCopy(\nthis.collection.getAllSorted()\n);\ndebug(`Collection: collections.all size: ${collections.all.length}`);\n\nlet tags = this.getAllTags();\nfor (let tag of tags) {\ncollections[tag] = this.createTemplateMapCopy(\nthis.collection.getFilteredByTag(tag)\n);\ndebug(`Collection: collections.${tag} size: ${collections[tag].length}`);\n}\nreturn collections;\n}\ngetTaggedCollectionsData() est appelée dans TemplateMap.cache() qui est\nl'endroit ou Eleventy génère les collections.\nLes collections sur mesure\nOutre les collections créées à partir de tags, vous pouvez utiliser la fonction\naddCollection() dans votre fichier de configuration .eleventy.js pour créer\nvos propres collections.\nPar exemple, voici comment créer une collection nommée articles constituée de pages\ngénérées à partir de modèles présents dans le dossier src\/articles\/ :\neleventyConfig.addCollection(\"articles\", (collection) =&gt;\n  collection\n    .getAllSorted()\n    .filter(\n      (item) =&gt;\n        item.url &amp;&amp;\n        !item.inputPath.includes(\"index.njk\") &amp;&amp;\n        item.inputPath.startsWith(\".\/src\/articles\/\")\n    )\n);\nLa fonction addCollection() prend deux paramètres1 :\n\nle nom de la collection (une chaîne de caractères)\nune fonction qui prend une collection en paramètre.\n\nVous pourriez penser que le paramètre collection est un tableau d'objets de\nmodèle comme l'objet collections basé sur les tags. Ce paramètre est en fait\nune instance d'une TemplateCollection, qui dérive de\nSortable, et ressemble à ceci :\n{\n  \"items\": [\n    { ... },\n    . . .\n    { ... }\n  ],\n  \"sortFunctionStringMap\": { ... },\n  \"sortAscending\": true,\n  \"sortNumeric\": false\n}\nLa propriété items est un tableau de tous les objets de modèle. C'est la même\nchose que collections.all. Vous ne voulez pas accéder aux éléments directement\nen écrivant : collection.item[n].\nUtilisez plutôt les méthodes suivantes pour accéder aux éléments.\n\n\n\nMéthode\nDescription\n\n\n\n\ngetAll()\nRécupérer tous les éléments dans un ordre spécifique.\n\n\ngetAllSorted()\nRécupérer tous les éléments dans l'ordre.\n\n\ngetFilteredByTag(tagName)\nRécupérer tous les éléments qui possèdent un tag spécifique.\n\n\ngetFilteredByGlob(glob)\nRécupérer tous les éléments dont l' inputPath correspond à un ou plusieurs patterns globaux.\n\n\n\nLes éléments sont presque les mêmes que ceux des\ncollections basées sur des tags, à la différence près que dans les collections\nbasées sur des tags, les éléments ont une propriété templateContent. Dans les\ncollections créées avec la fonction addCollection(), les éléments ont une\npropriété _pages. Je ne saurais dire pourquoi.\nVous pouvez utiliser addCollection() pour créer des collections de pages.\nDepuis Eleventy 0.5.3, vous pouvez l'utiliser pour créer des collections ou des\nobjets de votre choix.\nPar exemple, voici comment vous créeriez une collection constituée d'un tableau\nde toutes les catégories :\nmodule.exports = function (collection) {\n  let catSet = new Set();\n\n  collection\n    .getAllSorted()\n    .forEach(\n      (item) =&gt;\n        typeof item.data.category === \"string\" &amp;&amp; catSet.add(item.data.category)\n    );\n\n  return [...catSet];\n};\nImplémentation : Comment sont construites les collections sur mesure\ngetUserConfigCollectionsData() est la fonction qui appelle ce qui est retourné par la fonction addCollection().\nasync getUserConfigCollectionsData() {\nlet collections = {};\nlet configCollections =\nthis.configCollections || eleventyConfig.getCollections();\nfor (let name in configCollections) {\nlet ret = configCollections[name](this.collection);\n\/\/ work with arrays and strings returned from UserConfig.addCollection\nif (\nArray.isArray(ret) &amp;&amp;\nret.length &amp;&amp;\nret[0].inputPath &amp;&amp;\nret[0].outputPath\n) {\ncollections[name] = this.createTemplateMapCopy(ret);\n} else {\ncollections[name] = ret;\n}\ndebug(\n`Collection: collections.${name} size: ${collections[name].length}`\n);\n}\nreturn collections;\n}\ngetUserConfigCollectionsData() est appelé dans TemplateMap.cache() qui est\nl'endroit où Eleventy construit les collections.\n\n\n\n\naddCollection() ne fait rien d'autre qu'associer la fonction\nqui construit la collection au nom de la collection.\nLa fonction qui construit la collection est elle-même appelée plus tard dans\ngetUserConfigCollectionsData().&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<p>Dans Eleventy les <code>collections</code> permettent de grouper des articles selon divers\ncritères. Une collection pourrait désigner une série d'articles. Un autre\ncollection pourrait regrouper les articles à propos de livres. Une troisième\ncollection pourrait rassembler tous les contenus d'un même répertoire.</p>\n<p>Eleventy vous permet de créer des collections de deux manières :</p>\n<ul>\n<li><a href=\"#les-collections-à-base-de-tags\">implicitement</a>, à l'aide de tags dans le front matter</li>\n<li><a href=\"#les-collections-sur-mesure\">explicitement</a>, avec la fonction <code>addCollection()</code></li>\n</ul>\n<h2 id=\"les-collections-a-base-de-tags\">Les collections à base de tags</h2>\n<p>Toutes les pages qui partagent un même tag appartiennent à la même collection.\nUn modèle avec le front matter suivant va générer des pages dans les collections\n<code>books</code> et <code>reviews</code>.</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Finding</span> <span class=\"hljs-string\">Oz</span>\n<span class=\"hljs-attr\">category:</span> <span class=\"hljs-string\">Culture</span>\n<span class=\"hljs-attr\">tags:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">books</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">reviews</span>\n<span class=\"hljs-meta\">---</span>\n<span class=\"hljs-string\">.</span> <span class=\"hljs-string\">.</span> <span class=\"hljs-string\">.</span></code></pre>\n<p>Dans un modèle, on accède aux collections par leur nom, en tant que propriété de\nl'object global <code>collections</code>.</p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p</span>&gt;</span>\n  Le titre de cette page est :\n  </span><span class=\"hljs-template-variable\">{{ collections.books[0].data.title }}</span><span class=\"xml\">\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">p</span>&gt;</span></span></code></pre>\n<p>On utilise généralement les collections dans des boucles afin d'itérer sur\nchaque élément de la collection.</p>\n<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\">\n  </span><span class=\"hljs-template-variable\">{{ post.data.title }}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-variable\">{{ post.url }}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-variable\">{{ post.data.category }}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-variable\">{{ post.data.tags }}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-variable\">{{ post.<span class=\"hljs-name\">date</span> }}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<p>L'objet <code>collections</code> lui, ressemble à ça :</p>\n<pre><code class=\"language-json hljs json\">{\n  <span class=\"hljs-attr\">\"all\"</span>: [...],\n  <span class=\"hljs-attr\">\"nav\"</span>: [...],\n  <span class=\"hljs-attr\">\"books\"</span>: [\n    {\n      <span class=\"hljs-attr\">\"inputPath\"</span>: <span class=\"hljs-string\">\"./src/articles/finding-oz.md\"</span>,\n      <span class=\"hljs-attr\">\"outputPath\"</span>: <span class=\"hljs-string\">\"_site/articles/finding-oz/index.html\"</span>,\n      <span class=\"hljs-attr\">\"fileSlug\"</span>: <span class=\"hljs-string\">\"finding-oz\"</span>,\n      <span class=\"hljs-attr\">\"data\"</span>: {...},\n      <span class=\"hljs-attr\">\"date\"</span>: <span class=\"hljs-string\">\"2009-08-07T13:52:12.000Z\"</span>,\n      <span class=\"hljs-attr\">\"url\"</span>: <span class=\"hljs-string\">\"/articles/finding-oz/\"</span>,\n      <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>,\n      <span class=\"hljs-attr\">\"template\"</span>: {...}\n    },\n    ...\n  ],\n  <span class=\"hljs-attr\">\"programming\"</span>: [...],\n}</code></pre>\n<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\ncollection</a>\n(également appelés <a href=\"https://www.11ty.dev/docs/collections/#return-values\" target=\"_blank\" rel=\"noopener noreferrer\">objets\nmodèle</a> dans la\ndocumentation).</p>\n<p>La collection spéciale <code>all</code> représente un tableau de tous les objets page\ngénérés par Eleventy.</p>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Propriété</th>\n<th style=\"text-align: left;\">Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\"><code>inputPath</code></td>\n<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>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>outputPath</code></td>\n<td style=\"text-align: left;\">Chemin du fichier généré. <hr><code class=\"phony\">articles/finding-oz/index.html</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>fileSlug</code></td>\n<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>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>data</code></td>\n<td style=\"text-align: left;\">Données du front matter de la page rendue. Les variables globales disponibles pour chaque page.</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>date</code></td>\n<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>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>url</code></td>\n<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>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>templateContent</code></td>\n<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>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>template</code></td>\n<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>\n</tr>\n</tbody>\n</table>\n<p><strong>Implémentation : Comment un tag devient une collection</strong></p>\n<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>\n<pre><code class=\"language-javascript hljs javascript\"><span class=\"hljs-keyword\">async</span> getTaggedCollectionsData() {\n<span class=\"hljs-keyword\">let</span> collections = {};\ncollections.all = <span class=\"hljs-keyword\">this</span>.createTemplateMapCopy(\n<span class=\"hljs-keyword\">this</span>.collection.getAllSorted()\n);\ndebug(<span class=\"hljs-string\">`Collection: collections.all size: <span class=\"hljs-subst\">${collections.all.length}</span>`</span>);\n\n<span class=\"hljs-keyword\">let</span> tags = <span class=\"hljs-keyword\">this</span>.getAllTags();\n<span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">let</span> tag <span class=\"hljs-keyword\">of</span> tags) {\ncollections[tag] = <span class=\"hljs-keyword\">this</span>.createTemplateMapCopy(\n<span class=\"hljs-keyword\">this</span>.collection.getFilteredByTag(tag)\n);\ndebug(<span class=\"hljs-string\">`Collection: collections.<span class=\"hljs-subst\">${tag}</span> size: <span class=\"hljs-subst\">${collections[tag].length}</span>`</span>);\n}\n<span class=\"hljs-keyword\">return</span> collections;\n}</code></pre>\n<p><code>getTaggedCollectionsData()</code> est appelée dans <code>TemplateMap.cache()</code> qui est\nl'endroit ou Eleventy génère les collections.</p>\n<h2 id=\"les-collections-sur-mesure\">Les collections sur mesure</h2>\n<p>Outre les collections créées à partir de tags, vous pouvez utiliser la fonction\n<code>addCollection()</code> dans votre fichier de configuration <code>.eleventy.js</code> pour créer\nvos propres collections.</p>\n<p>Par exemple, voici comment créer une collection nommée <code>articles</code> constituée de pages\ngénérées à partir de modèles présents dans le dossier <code>src/articles/</code> :</p>\n<pre><code class=\"language-js hljs javascript\">eleventyConfig.addCollection(<span class=\"hljs-string\">\"articles\"</span>, (collection) =&gt;\n  collection\n    .getAllSorted()\n    .filter(\n      <span class=\"hljs-function\">(<span class=\"hljs-params\">item</span>) =&gt;</span>\n        item.url &amp;&amp;\n        !item.inputPath.includes(<span class=\"hljs-string\">\"index.njk\"</span>) &amp;&amp;\n        item.inputPath.startsWith(<span class=\"hljs-string\">\"./src/articles/\"</span>)\n    )\n);</code></pre>\n<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>\n<ul>\n<li>le nom de la collection (une chaîne de caractères)</li>\n<li>une fonction qui prend une <code>collection</code> en paramètre.</li>\n</ul>\n<p>Vous pourriez penser que le paramètre collection est un tableau d'objets de\nmodèle comme l'objet <code>collections</code> basé sur les tags. Ce paramètre est en fait\nune 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\n<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>\n<pre><code class=\"language-json hljs json\">{\n  <span class=\"hljs-attr\">\"items\"</span>: [\n    { ... },\n    . . .\n    { ... }\n  ],\n  <span class=\"hljs-attr\">\"sortFunctionStringMap\"</span>: { ... },\n  <span class=\"hljs-attr\">\"sortAscending\"</span>: <span class=\"hljs-literal\">true</span>,\n  <span class=\"hljs-attr\">\"sortNumeric\"</span>: <span class=\"hljs-literal\">false</span>\n}</code></pre>\n<p>La propriété <code>items</code> est un tableau de tous les objets de modèle. C'est la même\nchose que <code>collections.all</code>. Vous ne voulez pas accéder aux éléments directement\nen écrivant : <code>collection.item[n]</code>.\nUtilisez 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>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Méthode</th>\n<th style=\"text-align: left;\">Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\"><code>getAll()</code></td>\n<td style=\"text-align: left;\">Récupérer tous les éléments dans un ordre spécifique.</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>getAllSorted()</code></td>\n<td style=\"text-align: left;\">Récupérer tous les éléments dans l'ordre.</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>getFilteredByTag(tagName)</code></td>\n<td style=\"text-align: left;\">Récupérer tous les éléments qui possèdent un tag spécifique.</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>getFilteredByGlob(glob)</code></td>\n<td style=\"text-align: left;\">Récupérer tous les éléments dont l' <code>inputPath</code> correspond à un ou plusieurs patterns globaux.</td>\n</tr>\n</tbody>\n</table>\n<p>Les éléments sont <em>presque</em> <a href=\"#elements-collection\">les mêmes</a> que ceux des\ncollections basées sur des tags, à la différence près que dans les collections\nbasées sur des tags, les éléments ont une propriété <code>templateContent</code>. Dans les\ncollections créées avec la fonction <code>addCollection()</code>, les éléments ont une\npropriété <code>_pages</code>. Je ne saurais dire pourquoi.</p>\n<p>Vous pouvez utiliser <code>addCollection()</code> pour créer des collections de pages.\nDepuis Eleventy 0.5.3, vous pouvez l'utiliser pour créer des collections ou des\nobjets de votre choix.</p>\n<p>Par exemple, voici comment vous créeriez une collection constituée d'un tableau\nde toutes les catégories :</p>\n<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>{\n  <span class=\"hljs-keyword\">let</span> catSet = <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Set</span>();\n\n  collection\n    .getAllSorted()\n    .forEach(\n      <span class=\"hljs-function\">(<span class=\"hljs-params\">item</span>) =&gt;</span>\n        <span class=\"hljs-keyword\">typeof</span> item.data.category === <span class=\"hljs-string\">\"string\"</span> &amp;&amp; catSet.add(item.data.category)\n    );\n\n  <span class=\"hljs-keyword\">return</span> [...catSet];\n};</code></pre>\n<p><strong>Implémentation : Comment sont construites les collections sur mesure</strong></p>\n<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>\n<pre><code class=\"language-javascript hljs javascript\"><span class=\"hljs-keyword\">async</span> getUserConfigCollectionsData() {\n<span class=\"hljs-keyword\">let</span> collections = {};\n<span class=\"hljs-keyword\">let</span> configCollections =\n<span class=\"hljs-keyword\">this</span>.configCollections || eleventyConfig.getCollections();\n<span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">let</span> name <span class=\"hljs-keyword\">in</span> configCollections) {\n<span class=\"hljs-keyword\">let</span> ret = configCollections[name](<span class=\"hljs-keyword\">this</span>.collection);\n<span class=\"hljs-comment\">// work with arrays and strings returned from UserConfig.addCollection</span>\n<span class=\"hljs-keyword\">if</span> (\n<span class=\"hljs-built_in\">Array</span>.isArray(ret) &amp;&amp;\nret.length &amp;&amp;\nret[<span class=\"hljs-number\">0</span>].inputPath &amp;&amp;\nret[<span class=\"hljs-number\">0</span>].outputPath\n) {\ncollections[name] = <span class=\"hljs-keyword\">this</span>.createTemplateMapCopy(ret);\n} <span class=\"hljs-keyword\">else</span> {\ncollections[name] = ret;\n}\ndebug(\n<span class=\"hljs-string\">`Collection: collections.<span class=\"hljs-subst\">${name}</span> size: <span class=\"hljs-subst\">${collections[name].length}</span>`</span>\n);\n}\n<span class=\"hljs-keyword\">return</span> collections;\n}</code></pre>\n<p><code>getUserConfigCollectionsData()</code> est appelé dans <code>TemplateMap.cache()</code> qui est\nl'endroit où Eleventy construit les collections.</p>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:addcollection\">\n<p><code>addCollection()</code> ne fait rien d'autre qu'associer la fonction\nqui construit la collection au nom de la collection.\nLa fonction qui construit la collection est elle-même appelée plus tard dans\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2019/01/28/meetup-jamstack-paris-1/",
      "url": "https://jamstatic.fr/2019/01/28/meetup-jamstack-paris-1/",
      "title": "Meetup Jamstack Paris #1",
      "summary": "Résumé des deux présentations dédiées à Gatsby.",
      "date_published": "2019-01-28T17:45:24+00:00","content_text": "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. Les vidéos sont en ligne.\nDans la première présentation, Louis Lafont, développeur front-end chez MonBanquet, présente la stack qu'il a choisi pour la refonte du site, à savoir Gatsby pour la partie front, Contentful pour la gestion de contenu et Netlify pour héberger le tout.\nIl aura fallu près de deux mois à Louis pour mener à bien cette tâche et apprécier la developer experience offerte par Gatsby. Au final grâce notamment au plugin gatsby-image, la PWA générée est bien entendu beaucoup plus rapide, le Time to Interactive 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é.\nEt le tout pour la modique somme de zéro euro 😲, 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.\nTous le monde est ravi, la prochaine étape sera de s'attaquer à l'authentification utilisateur et d'ajouter un gestion de panier 💳 e-commerce.\n\n\n\n\nPour la deuxième présentation, Nicolas Goutay, é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 un projet qui liste les théâtres parisiens.\nGrand amateur de disques vinyles, Nicolas comme tout bon nerd a enregistré sa collection dans Discogs, qui propose une API pour récupérer les releases. Problème, le nombre d'appel est très limité, et son premier mockup, 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.\nL'application récupére les références des releases 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é un plugin Gatsby source, 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 developer experience offerte par Gatsby, avec notamment une gestion automatique de la documentation de tous les attributs des noeuds.\n\n\n\n\n👏 Un grand bravo aux organisateurs, le prochain meetup aura lieu le 27 février.\nAu programme encore du Gatsby, cette fois avec du Algolia et du WordPress dedans.\nOn 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.",
      "content_html": "<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>\n<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>\n<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>\n<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>\n<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>\n<p><div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div></p>\n<hr>\n<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>\n<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>\n<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>\n<p><div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div></p>\n<hr>\n<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>.\nAu programme encore du Gatsby, cette fois avec du Algolia et du WordPress dedans.</p>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/12/12/passer-de-jekyll-a-eleventy/",
      "url": "https://jamstatic.fr/2018/12/12/passer-de-jekyll-a-eleventy/",
      "title": "Passer de Jekyll à Eleventy",
      "summary": "Longtemps utilisateur du très populaire Jekyll, Paul Lloyd a décidé de passer à la vitesse supérieure avec Eleventy.",
      "date_published": "2018-12-12T00:00:00+00:00","content_text": "Jekyll 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 Eleventy, développé par le très sympathique Zach Leat. 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.\nAutant d'arguments qui ont vite fait de convaincre Paul Robert Lloyd de tenter de migrer son site vers Eleventy. Qui sait, la lecture de cet article vous incitera peut-être à faire de même ?\nNe 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.\nLa Jamstack (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 Movable Type, et puis récemment avec celle de Jekyll, à propos duquel Anna écrivait en 2013.\nEn 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 beaucoup de générateurs de sites statiques 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 Atwoodien et a déposé un tel générateur de site statique au pied de mon sapin.\nPrésentation d'Eleventy\nEleventy 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 bien meilleurs (et les optimisations futures promettent des gains supplémentaires).\nVu 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 parcours1.\nTout au long de cet article, nous allons prendre comme exemple le site du Guide Markdown de Matt Cone. Si vous voulez tester les modifications, commencez par cloner le dépôt git et placez-vous dans le dossier du projet :\ngit clone https:\/\/github.com\/mattcone\/markdown-guide.git\ncd markdown-guide\nAvant de commencer\nSi 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'installer Node.js et de configurer votre projet afin de pouvoir utiliser son gestionnaire de paquet, npm :\n\nInstaller Node.js :\n\n\nMac : Si ce n'est pas déjà fait, je vous conseille d'installer Homebrew, a gestionnaire de paquets pour Mac. Ensuite dans un terminal tapez brew install node.\nWindows : Téléchargez l'installateur pour Windows depuis le site web de Node.js et suivez les instructions.\n\n\nInitialiser NPM : Assurez-vous d'être dans le répertoire du projet et tapez npm init. Cette commande va vous poser quelques questions avant de créer un fichier appelé package.json. Comme le Gemfile de RubyGems, il contient la liste des dépendances tierces de votre projet.\n\nSi vous gérez les versions de votre site avec Git, assurez-vous également d'ajouter le répertoire node_modules à votre fichier .gitignore. 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.\nInstaller Eleventy\nMaintenant que Node.js est installé et que votre projet est prêt à utiliser npm, nous pouvons installer Eleventy en tant que dépendance :\nnpm install --save-dev @11ty\/eleventy\nSi vous ouvrez votre package.json vous devriez y voir figurer les lignes suivantes :\n…\n\"devDependencies\": {\n  \"@11ty\/eleventy\": \"^0.6.0\"\n}\n…\nNous pouvons maintenant lancer Eleventy en ligne de commande à l'aide de l'utilitaire npx de NPM. Par exemple pour convertir le fichier README.md en HTML, nous taperons la commande suivante :\nnpx eleventy --input=README.md --formats=md\nCette commande va générer un fichier HTML dans _site\/README\/index.html.\nEleventy utilise par défaut le même répertoire de destination que Jekyll (_site), comme nous le verrons à de nombreuses reprises pendant cette transition.\nLa configuration\nAlors 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.\nCommençons par créer notre fichier de configuration (.eleventy.js), et reportons les paramètres pertinents du _config.yml dans leurs options équivalentes :\nmodule.exports = function(eleventyConfig) {\n  return {\n    dir: {\n      input: \".\/\",      \/\/ Équivalent au paramètre source de Jekyll\n      output: \".\/_site\" \/\/ Équivalent au paramètre destination de Jekyll\n    }\n  };\n};\nQuelques choses bonnes à savoir :\n\nAlors que Jekyll vous permet de lister les fichiers et dossiers à exclure de la génération avec le paramètre exclude, Eleventy recherche ses mêmes valeurs dans un fichier nommé  .eleventyignore (en plus du .gitignore).\nPar défaut, Eleventy utilise markdown-it 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 déclarer votre propre instance de cette bibliothèque Markdown (ou d'une autre) à Eleventyet la configurer avec les options et les plugins de votre choix.\n\nLes gabarits de mise en forme\nEleventy manque encore de flexibilité quant à la localisation des layouts, qui doivent pour le moment se trouver dans le répertoire _includes (Surveiller la résolution du problème sur GitHub).\nNous allons donc devoir déplacer nos fichiers du répertoire _layouts vers _includes\\layouts, puis mettre à jour les références pour y incorporer le sous-dossier layouts. Nous pourrions mettre à jour la propriété layout: dans le front matter de chacun de nos fichiers de contenu, mais nous allons opter pour la création d'alias dans la configuration d'Eleventy :\nmodule.exports = function(eleventyConfig) {\n    \/\/ les alias sont relatifs au répertoire _includes\n    eleventyConfig.addLayoutAlias('about', 'layouts\/about.html');\n    eleventyConfig.addLayoutAlias('book', 'layouts\/book.html');\n    eleventyConfig.addLayoutAlias('default', 'layouts\/default.html');\n\n    return {\n      dir: {\n        input: \".\/\",\n        output: \".\/_site\"\n      }\n    };\n  }\nDéterminer le langage à utiliser pour les gabarits\nPar défaut Eleventy va transformer les fichiers Markdown (.md) 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 plusieurs manières de faire, la plus simple étant de modifier les extensions des fichiers. Ici, quelques fichiers se trouvent dans notre dossier api que nous voulons traiter avec Liquid et exporter au format JSON. Pour cela nous ajoutons le suffixe .liquid à notre fichier (en conséquence basic-syntax.json devient basic-syntax.json.liquid), Eleventy saura alors quoi faire.\nLes variables\nDe 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.\nLes variables de site\nEn 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 site.*. Par exemple dans notre Guide Markdown nous avons les valeurs suivantes :\ntitle: \"Markdown Guide\"\nurl: https:\/\/www.markdownguide.org\nbaseurl: \"\"\nrepo: http:\/\/github.com\/mattcone\/markdown-guide\ncomments: false\nauthor:\n  name: \"Matt Cone\"\nog_locale: \"en_US\"\nLe fichier de configuration d'Eleventy utilise JavaScript et n'est pas fait pour stocker de telles valeurs. Toutefois comme avec Jekyll, nous pouvons utiliser des fichiers de données pour stocker des variables globales. Si nous ajoutons nos données relatives au site dans un fichier JSON situé dans le dossier _data et que nous le nommons site.json, nous pouvons continuer à utiliser l'espace de nom site.* et laisser nos variables telles quelles.\n{\n    \"title\": \"Markdown Guide\",\n    \"url\": \"https:\/\/www.markdownguide.org\",\n    \"baseurl\": \"\",\n    \"repo\": \"http:\/\/github.com\/mattcone\/markdown-guide\",\n    \"comments\": false,\n    \"author\": {\n      \"name\": \"Matt Cone\"\n    },\n    \"og_locale\": \"en_US\"\n  }\nLes variables de page\nLe tableau ci-dessous établit la correspondance entre les variables de page.\nRetenez 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 pages.* :\n\n\n\nJekyll\nEleventy\n\n\n\n\npage.url\npage.url\n\n\npage.date\npage.date\n\n\npage.path\npage.inputPath\n\n\npage.id\npage.outputPath\n\n\npage.name\npage.fileSlug\n\n\npage.content\ncontent\n\n\npage.title\ntitle\n\n\npage.foobar\nfoobar\n\n\n\nLorsque d'une itération sur des pages, les valeurs front matter sont accessibles via l'objet data et le contenu via templateContent :\n\n\n\nJekyll\nEleventy\n\n\n\n\nitem.url\nitem.url\n\n\nitem.date\nitem.date\n\n\nitem.path\nitem.inputPath\n\n\nitem.id\nitem.outputPath\n\n\nitem.name\nitem.fileSlug\n\n\nitem.content\nitem.templateContent\n\n\nitem.title\nitem.data.title\n\n\nitem.foobar\nitem.data.foobar\n\n\n\nEspérons que ces différences entre les pages et les variables d'item disparaissent dans une future version (suivre le problème sur GitHub), afin de faciliter la compréhension de la manière dont Eleventy structure ses données.\nLes variables de pagination\nAlors qu'avec Jekyll, la pagination est limitée à lister des articles sur une page, Eleventy vous permet de paginer n'importe quelles données ou documents de collections. Vu cette disparité, les changements sont plus importants, mais ce tableau liste la correspondance des variables équivalentes :\n\n\n\nJekyll\nEleventy\n\n\n\n\npagination.page\npagination.pageNumber\n\n\npagination.per_page\npagination.size\n\n\npagination.posts\npagination.items\n\n\npagination.previous_page_path\npagination.previousPageHref\n\n\npagination.previous_page_path\npagination.nextPageHref\n\n\n\nLes filtres\nJekyll propose quelques filtres supplémentaires, en plus de ceux fournis par défaut par Liquid.\nIl y en a un certain nombre — cet article ne peut pas tous les couvrir — mais vous pouvez les répliquer avec l'option de configuration addFilter d'Eleventy. Convertissons les deux filtres utilisés par notre Guide Markdown : jsonify et where.\nLe filtre jsonify sert à exporter un objet ou une chaîne de caractères dans un format JSON valide. Comme JavaScript propose une méthode JSON native pour cela, nous pouvons l'utiliser dans notre filtre. La méthode addFilter 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 :\n\/\/ {{ variable | jsonify }}\n  eleventyConfig.addFilter('jsonify', function (variable) {\n    return JSON.stringify(variable);\n  });\nLe filtre where 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 :\n{{ site.members | where: \"graduation_year\",\"2014\" }}\nPour reproduire ce comportement, nous pouvons passer trois arguments au lieu d'un à la fonction passée à addFilter: le tableau (array) que nous voulons examiner, la clef sur laquelle on veut effectuer la recherche et la valeur recherchée :\n\/\/ {{ array | where: key,value }}\n  eleventyConfig.addFilter('where', function (array, key, value) {\n    return array.filter(item =&gt; {\n      const keys = key.split('.');\n      const reducedKey = keys.reduce((object, key) =&gt; {\n        return object[key];\n      }, item);\n\n      return (reducedKey === value ? item : false);\n    });\n  });\nIl se passe pas mal de trucs dans ce filtre, que je vais tenter d'expliquer.\nNous examinons chaque item dans notre array, et nous réduisons la key (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 value. Si elle correspond, l'item reste dans le tableau retourné, sinon il est supprimé. Pfiou !\nLes includes\nComme pour les filtres, Jekyll fournit un jeu de tags qui ne fait pas partie intégrante du cœur de Liquid. Parmi eux, l'un des plus utiles est le tag include. La bibliothèque utilisée par Eleventy, LiquidJS fournit aussi un tag include, mais sa syntaxe diffère légèrement de celle définie par Shopify. 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.\nDans le cas contraire, alors qu'avec Jekyll vous écrivez :\n&lt;!-- page.html --&gt;\n{% include include.html value=\"key\" %}\n\n&lt;!-- include.html --&gt;\n{{ include.value }}\ndans Eleventy, vous allez écrire :\n&lt;!-- page.html --&gt;\n{% include \"include.html\", value: \"key\" %}\n\n&lt;!-- include.html --&gt;\n{{ value }}\nL'inconvénient de la syntaxe Shopify c'est que les assignations de variables ne sont plus limitées au périmètre de l'include 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.\nParamétrer Liquid\nVous 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`dynamicPartialsde LiquidJS àfalse\\.\nEn outre, Eleventy ne supporte pas le tag include_relative, 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 root.\nHeureusement pour nous, Eleventy nous laisse passer des options à LiquidJS :\neleventyConfig.setLiquidOptions({\n    dynamicPartials: false,\n    root: [\n      '_includes',\n      '.'\n    ]\n  });\nLes collections\nDans Jekyll les collections permettent aux auteurs de créer les collections de documents de leur choix, en plus des pages et des articles. Eleventy propose une fonctionnalité similaire, mais qui permet de faire beaucoup plus de choses.\nLes collections dans Jekyll\nDans Jekyll, pour créer des collections, vous devez ajouter leurs noms dans le fichier _config.yml et créer les dossiers correspondants dans votre projet. Notre guide Markdown possède deux collections :\ncollections:\n    - basic-syntax\n    - extended-syntax\nElles correspondent aux dossiers _basic-syntax et _extended-syntax, nous pouvons itérer sur leurs contenus de la sorte :\n{% for syntax in site.extended-syntax %}\n  {{ syntax.title }}\n{% endfor %}\nLes collections dans Eleventy\nIl existe deux manières de configurer des collections dans Eleventy.\nTout d'abord, en utilisant simplement la propriété tag dans le front matter des fichiers de contenu :\n---\ntitle: Barré\nsyntax-id: barre\nsyntax-summary: \"~~La terre est plate~~\"\ntag: extended-syntax\n---\nNous pouvons ensuite itérer sur les contenus étiquetés de la sorte :\n{% for syntax in collections.extended-syntax %}\n  {{ syntax.data.title }}\n{% endfor %}\nEleventy permet aussi de déclarer des collections à l'aide de la fonction addCollection. 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) :\neleventyConfig.addCollection('basic-syntax', collection =&gt; {\n  return collection.getFilteredByGlob('_basic-syntax\/*.md');\n});\n\neleventyConfig.addCollection('extended-syntax', collection =&gt; {\n  return collection.getFilteredByGlob('_extended-syntax\/*.md');\n});\nNous pouvons faire encore mieux. Par exemple, imaginons que nous voulions trier une collection par la propriété display_order du front matter de nos documents. Nous pourrions prendre les résultats retournés par la fonction collection.getFilteredByGlob et les trier à l'aide de la fonction sort de JavaScript :\neleventyConfig.addCollection('example', collection =&gt; {\n  return collection.getFilteredByGlob('_examples\/*.md').sort((a, b) =&gt; {\n    return a.data.display_order - b.data.display_order;\n  });\n});\nAvec un peu de chance, cet exemple vous a fait comprendre ce qu'il est possible de faire avec cette approche.\nUtiliser les données de répertoire pour définir les paramètres par défaut\nPar 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\n\/_basic-syntax\/lists.md sera généré sous \/_basic-syntax\/lists\/index.html.\nComme dans Jekyll, nous pouvons définir où les fichiers seront générés à l'aide de la propriété permalink. Par exemple si nous voulons que cette page devienne accessible sous \/basic-syntax\/lists.html nous pouvons ajouter :\n---\ntitle: Lists\nsyntax-id: lists\napi: \"no\"\npermalink: \/basic-syntax\/lists.html\n---\nLà 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 : les données de dossier et les variables pour les permaliens.\nPar exemple, pour parvenir au même résultat que précédemment pour tous les contenus stockés dans le dossier _basic-syntax, nous pouvons y créer un fichier JSON du même nom, _basic-syntax\/_basic-syntax.json 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é :\n{\n  \"layout\": \"syntax\",\n  \"tag\": \"basic-syntax\",\n  \"permalink\": \"basic-syntax\/{{ title | slug }}.html\"\n}\nMaintenant, 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é _content :\nmarkdown-guide\n└── _content\n    ├── basic-syntax\n    ├── extended-syntax\n    ├── getting-started\n    └── _content.json\nAjoutons également un fichier de données (_content.json) 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 :\n{\n  \"permalink\": false\n}\nLes fichiers statiques\nEleventy 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 la copie de fichier \"passe-plat\". Dans notre fichier de configuration, nous indiquons à Eleventy quels dossiers\/fichiers copier via l'option addPassthroughCopy.\nPuis nous activons cette fonctionnalité dans ce qui est retourné, en mettant passthroughFileCopy à true :\nmodule.exports = function(eleventyConfig) {\n  …\n\n  \/\/ Copy the `assets` directory to the compiled site folder\n  eleventyConfig.addPassthroughCopy('assets');\n\n  return {\n    dir: {\n      input: \".\/\",\n      output: \".\/_site\"\n    },\n    passthroughFileCopy: true\n  };\n}\nConsidérations finales\nGestion des assets\nContrairement à 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.\nPublication sur GitHub Pages\nUn des gros avantages de Jekyll, c'est son intégration dans GitHub Pages. 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 copier le site généré dans la branche gh-pages ou d'inclure cette branche comme un sous-module Git. Vous pouvez aussi utiliser un service d'intégration continue comme Travis ou CircleCI et pousser le site généré sur votre serveur web. De quoi vous faire tourner la tête !\nC'est peut-être pour cette raison que des services spécialisés dans l'hébergement de fichiers statiques ont émergé comme Netlify ou Google Firebase.\nRappelez-vous cependant que vous pouvez publier un site statique où vous voulez !\nMontez d'un cran\nSi 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.\nEssayer 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.\nJ'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 !\nMais ceux-là vont jusqu'à 11.\n\n\n\n\nL'information présentée ici est valable pour les versions 0.6.0 d'Eleventy et 3.8.5 de Jekyll.&#160;&#8617;\n\n\n",
      "content_html": "<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>\nAutant 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>\n<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>\n<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>\n<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>\n<h2 id=\"presentation-d-eleventy\">Présentation d'Eleventy</h2>\n<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>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">git <span class=\"hljs-built_in\">clone</span> https://github.com/mattcone/markdown-guide.git\n<span class=\"hljs-built_in\">cd</span> markdown-guide</code></pre></aside>\n<h2 id=\"avant-de-commencer\">Avant de commencer</h2>\n<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>\n<ol>\n<li><strong>Installer Node.js :</strong></li>\n</ol>\n<ul>\n<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>\n<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>\n</ul>\n<ol start=\"2\">\n<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>\n</ol>\n<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>\n<h2 id=\"installer-eleventy\">Installer Eleventy</h2>\n<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>\n<pre><code class=\"language-sh hljs bash\">npm install --save-dev @11ty/eleventy</code></pre>\n<p>Si vous ouvrez votre <code>package.json</code> vous devriez y voir figurer les lignes suivantes :</p>\n<pre><code class=\"language-sh hljs bash\">…\n<span class=\"hljs-string\">\"devDependencies\"</span>: {\n  <span class=\"hljs-string\">\"@11ty/eleventy\"</span>: <span class=\"hljs-string\">\"^0.6.0\"</span>\n}\n…</code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">npx eleventy --input=README.md --formats=md</code></pre>\n<p>Cette commande va générer un fichier HTML dans <code>_site/README/index.html</code>.<br>\nEleventy 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>\n<h2 id=\"la-configuration\">La configuration</h2>\n<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>\n<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>\n<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>{\n  <span class=\"hljs-keyword\">return</span> {\n    <span class=\"hljs-attr\">dir</span>: {\n      <span class=\"hljs-attr\">input</span>: <span class=\"hljs-string\">\"./\"</span>,      <span class=\"hljs-comment\">// Équivalent au paramètre source de Jekyll</span>\n      <span class=\"hljs-attr\">output</span>: <span class=\"hljs-string\">\"./_site\"</span> <span class=\"hljs-comment\">// Équivalent au paramètre destination de Jekyll</span>\n    }\n  };\n};</code></pre>\n<p>Quelques choses bonnes à savoir :</p>\n<ul>\n<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>\n<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>\n</ul>\n<h2 id=\"les-gabarits-de-mise-en-forme\">Les gabarits de mise en forme</h2>\n<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>\n<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>\n<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>{\n    <span class=\"hljs-comment\">// les alias sont relatifs au répertoire _includes</span>\n    eleventyConfig.addLayoutAlias(<span class=\"hljs-string\">'about'</span>, <span class=\"hljs-string\">'layouts/about.html'</span>);\n    eleventyConfig.addLayoutAlias(<span class=\"hljs-string\">'book'</span>, <span class=\"hljs-string\">'layouts/book.html'</span>);\n    eleventyConfig.addLayoutAlias(<span class=\"hljs-string\">'default'</span>, <span class=\"hljs-string\">'layouts/default.html'</span>);\n\n    <span class=\"hljs-keyword\">return</span> {\n      <span class=\"hljs-attr\">dir</span>: {\n        <span class=\"hljs-attr\">input</span>: <span class=\"hljs-string\">\"./\"</span>,\n        <span class=\"hljs-attr\">output</span>: <span class=\"hljs-string\">\"./_site\"</span>\n      }\n    };\n  }</code></pre>\n<h3 id=\"determiner-le-langage-a-utiliser-pour-les-gabarits\">Déterminer le langage à utiliser pour les gabarits</h3>\n<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>\n<h2 id=\"les-variables\">Les variables</h2>\n<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>\n<h3 id=\"les-variables-de-site\">Les variables de site</h3>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">\"Markdown Guide\"</span>\n<span class=\"hljs-attr\">url:</span> <span class=\"hljs-string\">https://www.markdownguide.org</span>\n<span class=\"hljs-attr\">baseurl:</span> <span class=\"hljs-string\">\"\"</span>\n<span class=\"hljs-attr\">repo:</span> <span class=\"hljs-string\">http://github.com/mattcone/markdown-guide</span>\n<span class=\"hljs-attr\">comments:</span> <span class=\"hljs-literal\">false</span>\n<span class=\"hljs-attr\">author:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">\"Matt Cone\"</span>\n<span class=\"hljs-attr\">og_locale:</span> <span class=\"hljs-string\">\"en_US\"</span></code></pre>\n<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>\n<pre><code class=\"language-json hljs json\">{\n    <span class=\"hljs-attr\">\"title\"</span>: <span class=\"hljs-string\">\"Markdown Guide\"</span>,\n    <span class=\"hljs-attr\">\"url\"</span>: <span class=\"hljs-string\">\"https://www.markdownguide.org\"</span>,\n    <span class=\"hljs-attr\">\"baseurl\"</span>: <span class=\"hljs-string\">\"\"</span>,\n    <span class=\"hljs-attr\">\"repo\"</span>: <span class=\"hljs-string\">\"http://github.com/mattcone/markdown-guide\"</span>,\n    <span class=\"hljs-attr\">\"comments\"</span>: <span class=\"hljs-literal\">false</span>,\n    <span class=\"hljs-attr\">\"author\"</span>: {\n      <span class=\"hljs-attr\">\"name\"</span>: <span class=\"hljs-string\">\"Matt Cone\"</span>\n    },\n    <span class=\"hljs-attr\">\"og_locale\"</span>: <span class=\"hljs-string\">\"en_US\"</span>\n  }</code></pre>\n<h3 id=\"les-variables-de-page\">Les variables de page</h3>\n<p>Le tableau ci-dessous établit la correspondance entre les variables de page.\nRetenez 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>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Jekyll</th>\n<th style=\"text-align: left;\">Eleventy</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\"><code>page.url</code></td>\n<td style=\"text-align: left;\"><code>page.url</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>page.date</code></td>\n<td style=\"text-align: left;\"><code>page.date</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>page.path</code></td>\n<td style=\"text-align: left;\"><code>page.inputPath</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>page.id</code></td>\n<td style=\"text-align: left;\"><code>page.outputPath</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>page.name</code></td>\n<td style=\"text-align: left;\"><code>page.fileSlug</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>page.content</code></td>\n<td style=\"text-align: left;\"><code>content</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>page.title</code></td>\n<td style=\"text-align: left;\"><code>title</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>page.foobar</code></td>\n<td style=\"text-align: left;\"><code>foobar</code></td>\n</tr>\n</tbody>\n</table>\n<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>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Jekyll</th>\n<th style=\"text-align: left;\">Eleventy</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\"><code>item.url</code></td>\n<td style=\"text-align: left;\"><code>item.url</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>item.date</code></td>\n<td style=\"text-align: left;\"><code>item.date</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>item.path</code></td>\n<td style=\"text-align: left;\"><code>item.inputPath</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>item.id</code></td>\n<td style=\"text-align: left;\"><code>item.outputPath</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>item.name</code></td>\n<td style=\"text-align: left;\"><code>item.fileSlug</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>item.content</code></td>\n<td style=\"text-align: left;\"><code>item.templateContent</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>item.title</code></td>\n<td style=\"text-align: left;\"><code>item.data.title</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>item.foobar</code></td>\n<td style=\"text-align: left;\"><code>item.data.foobar</code></td>\n</tr>\n</tbody>\n</table>\n<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>\n<h3 id=\"les-variables-de-pagination\">Les variables de pagination</h3>\n<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>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Jekyll</th>\n<th style=\"text-align: left;\">Eleventy</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\"><code>pagination.page</code></td>\n<td style=\"text-align: left;\"><code>pagination.pageNumber</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>pagination.per_page</code></td>\n<td style=\"text-align: left;\"><code>pagination.size</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>pagination.posts</code></td>\n<td style=\"text-align: left;\"><code>pagination.items</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>pagination.previous_page_path</code></td>\n<td style=\"text-align: left;\"><code>pagination.previousPageHref</code></td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>pagination.previous_page_path</code></td>\n<td style=\"text-align: left;\"><code>pagination.nextPageHref</code></td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"les-filtres\">Les filtres</h2>\n<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.\nIl 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>\n<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>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-comment\">// {{ variable | jsonify }}</span>\n  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>{\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-built_in\">JSON</span>.stringify(variable);\n  });</code></pre>\n<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>\n<pre><code class=\"language-twig hljs twig\"><span class=\"hljs-template-variable\">{{ site.members | where: \"graduation_year\",\"2014\" }}</span></code></pre>\n<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>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-comment\">// {{ array | where: key,value }}</span>\n  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>{\n    <span class=\"hljs-keyword\">return</span> array.filter(<span class=\"hljs-function\"><span class=\"hljs-params\">item</span> =&gt;</span> {\n      <span class=\"hljs-keyword\">const</span> keys = key.split(<span class=\"hljs-string\">'.'</span>);\n      <span class=\"hljs-keyword\">const</span> reducedKey = keys.reduce(<span class=\"hljs-function\">(<span class=\"hljs-params\">object, key</span>) =&gt;</span> {\n        <span class=\"hljs-keyword\">return</span> object[key];\n      }, item);\n\n      <span class=\"hljs-keyword\">return</span> (reducedKey === value ? item : <span class=\"hljs-literal\">false</span>);\n    });\n  });</code></pre>\n<p>Il se passe pas mal de trucs dans ce filtre, que je vais tenter d'expliquer.<br>\nNous 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>\n<h2 id=\"les-includes\">Les includes</h2>\n<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>\nDans le cas contraire, alors qu'avec Jekyll vous écrivez :</p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\"><span class=\"hljs-comment\">&lt;!-- page.html --&gt;</span>\n</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\">\n\n<span class=\"hljs-comment\">&lt;!-- include.html --&gt;</span>\n</span><span class=\"hljs-template-variable\">{{ <span class=\"hljs-name\">include</span>.value }}</span></code></pre>\n<p>dans Eleventy, vous allez écrire :</p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\"><span class=\"hljs-comment\">&lt;!-- page.html --&gt;</span>\n</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\">\n\n<span class=\"hljs-comment\">&lt;!-- include.html --&gt;</span>\n</span><span class=\"hljs-template-variable\">{{ value }}</span></code></pre>\n<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>\n<h3 id=\"parametrer-liquid\">Paramétrer Liquid</h3>\n<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>\n<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>\n<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>\n<pre><code class=\"language-js hljs javascript\">eleventyConfig.setLiquidOptions({\n    <span class=\"hljs-attr\">dynamicPartials</span>: <span class=\"hljs-literal\">false</span>,\n    <span class=\"hljs-attr\">root</span>: [\n      <span class=\"hljs-string\">'_includes'</span>,\n      <span class=\"hljs-string\">'.'</span>\n    ]\n  });</code></pre>\n<h2 id=\"les-collections\">Les collections</h2>\n<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>\n<h3 id=\"les-collections-dans-jekyll\">Les collections dans Jekyll</h3>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">collections:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">basic-syntax</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">extended-syntax</span></code></pre>\n<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>\n<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\">\n  </span><span class=\"hljs-template-variable\">{{ syntax.title }}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<h3 id=\"les-collections-dans-eleventy\">Les collections dans Eleventy</h3>\n<p>Il existe deux manières de configurer des collections dans Eleventy.\nTout d'abord, en utilisant simplement la propriété <code>tag</code> dans le front matter des fichiers de contenu :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Barré</span>\n<span class=\"hljs-attr\">syntax-id:</span> <span class=\"hljs-string\">barre</span>\n<span class=\"hljs-attr\">syntax-summary:</span> <span class=\"hljs-string\">\"~~La terre est plate~~\"</span>\n<span class=\"hljs-attr\">tag:</span> <span class=\"hljs-string\">extended-syntax</span>\n<span class=\"hljs-meta\">---</span></code></pre>\n<p>Nous pouvons ensuite itérer sur les contenus étiquetés de la sorte :</p>\n<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\">\n  </span><span class=\"hljs-template-variable\">{{ syntax.data.title }}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<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>\n<pre><code class=\"language-js hljs javascript\">eleventyConfig.addCollection(<span class=\"hljs-string\">'basic-syntax'</span>, collection =&gt; {\n  <span class=\"hljs-keyword\">return</span> collection.getFilteredByGlob(<span class=\"hljs-string\">'_basic-syntax/*.md'</span>);\n});\n\neleventyConfig.addCollection(<span class=\"hljs-string\">'extended-syntax'</span>, collection =&gt; {\n  <span class=\"hljs-keyword\">return</span> collection.getFilteredByGlob(<span class=\"hljs-string\">'_extended-syntax/*.md'</span>);\n});</code></pre>\n<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>\n<pre><code class=\"language-js hljs javascript\">eleventyConfig.addCollection(<span class=\"hljs-string\">'example'</span>, collection =&gt; {\n  <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> {\n    <span class=\"hljs-keyword\">return</span> a.data.display_order - b.data.display_order;\n  });\n});</code></pre>\n<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>\n<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>\n<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\n<code>/_basic-syntax/lists.md</code> sera généré sous <code>/_basic-syntax/lists/index.html</code>.\nComme 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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Lists</span>\n<span class=\"hljs-attr\">syntax-id:</span> <span class=\"hljs-string\">lists</span>\n<span class=\"hljs-attr\">api:</span> <span class=\"hljs-string\">\"no\"</span>\n<span class=\"hljs-attr\">permalink:</span> <span class=\"hljs-string\">/basic-syntax/lists.html</span>\n<span class=\"hljs-meta\">---</span></code></pre>\n<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>\n<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>\n<pre><code class=\"language-json hljs json\">{\n  <span class=\"hljs-attr\">\"layout\"</span>: <span class=\"hljs-string\">\"syntax\"</span>,\n  <span class=\"hljs-attr\">\"tag\"</span>: <span class=\"hljs-string\">\"basic-syntax\"</span>,\n  <span class=\"hljs-attr\">\"permalink\"</span>: <span class=\"hljs-string\">\"basic-syntax/{{ title | slug }}.html\"</span>\n}</code></pre>\n<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>\n<pre><code class=\"language-txt\">markdown-guide\n└── _content\n    ├── basic-syntax\n    ├── extended-syntax\n    ├── getting-started\n    └── _content.json</code></pre>\n<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>\n<pre><code class=\"language-json hljs json\">{\n  <span class=\"hljs-attr\">\"permalink\"</span>: <span class=\"hljs-literal\">false</span>\n}</code></pre>\n<h2 id=\"les-fichiers-statiques\">Les fichiers statiques</h2>\n<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>.\nPuis nous activons cette fonctionnalité dans ce qui est retourné, en mettant <code>passthroughFileCopy</code> à <code>true</code> :</p>\n<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>{\n  …\n\n  <span class=\"hljs-comment\">// Copy the `assets` directory to the compiled site folder</span>\n  eleventyConfig.addPassthroughCopy(<span class=\"hljs-string\">'assets'</span>);\n\n  <span class=\"hljs-keyword\">return</span> {\n    <span class=\"hljs-attr\">dir</span>: {\n      <span class=\"hljs-attr\">input</span>: <span class=\"hljs-string\">\"./\"</span>,\n      <span class=\"hljs-attr\">output</span>: <span class=\"hljs-string\">\"./_site\"</span>\n    },\n    <span class=\"hljs-attr\">passthroughFileCopy</span>: <span class=\"hljs-literal\">true</span>\n  };\n}</code></pre>\n<h2 id=\"considerations-finales\">Considérations finales</h2>\n<h3 id=\"gestion-des-assets\">Gestion des assets</h3>\n<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>\n<h3 id=\"publication-sur-github-pages\">Publication sur GitHub Pages</h3>\n<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>\n<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>.\nRappelez-vous cependant que vous pouvez publier un site statique où vous voulez !</p>\n<h2 id=\"montez-d-un-cran\">Montez d'un cran</h2>\n<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>\n<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>\n<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>\n<p>Mais <a href=\"https://www.11ty.dev/docs/#sites-using-eleventy\" target=\"_blank\" rel=\"noopener noreferrer\">ceux-là vont jusqu'à 11</a>.</p>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/12/07/oubliez-docker-le-futur-c-est-la-jamstack/",
      "url": "https://jamstatic.fr/2018/12/07/oubliez-docker-le-futur-c-est-la-jamstack/",
      "title": "Oubliez Docker, le futur c'est la Jamstack",
      "summary": "À 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.",
      "date_published": "2018-12-07T00:21:13+00:00",
      "date_modified": "2018-01-26T00:09:11+00:00","content_text": "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.\nÀ l'heure où les entreprises se débattent pour devenir plus agiles et rester\npertinentes, elles peuvent compter sur les dernières évolutions des\ntechnologies. Oubliez Docker, Jamstack marque la prochaine évolution du\ndéveloppement web moderne.\nJamstack c'est pour JavaScript, APis et Markup. C'est la merveilleuse union de\ntechnologies modernes, du logiciel as a service et des langages au coeur des\nfondations du web. Cette stack procure :\n\nune réduction des coûts,\nune mise sur le marché plus rapide,\nune sécurité accrue,\nplus de fiabilité, de disponibilité, et une meilleure adaptation à la montée en charge,\nune réduction de la dépendance à des technologies propriétaires.\n\nLes technologies comme Docker vont continuer à jouer un rôle primordial dans le\nfutur du numérique, mais elles devraient devenir de plus en plus transparentes\npour la majorité des entreprises et seront utilisées sans que vous le sachiez.\nLe fait de devoir gérer et maintenir soi-même sa propre infrastructure\nappartiendra bientôt au passé.\nSi votre coeur de métier n'est pas de fournir des services dans le Cloud, cet\narticle vous explique pourquoi la Jamstack devrait devenir votre première\npréoccupation si vous tenez à fournir un avantage stratégique et une agilité\naccrue à la majorité de vos activités en ligne.\nLa Jamstack ?\nLa Jamstack c'est en partie du JavaScript et du code généré qui peuvent être\nhébergés en statique n'importe où. Elle est parfaitement complémentaire avec les\ntechnologies serverless et peut faire appel à des fonctions serverless exécutées\ndans le Cloud. Les serveurs d'applications traditionnelles sont en passe d'être\ntotalement superflus, en leur lieu et place une bonne partie de la complexité\nest résolue lors de l'étape de génération à l'aide de l'outillage fourni par\nentre autres par JavaScript.\nDes générateurs de site statiques comme Next.js ou Gatsby sont souvent utilisés\npour combler le besoin de faire du rendu côté serveur. Ces outils s'intègrent\nparfaitement avec des services du Cloud pendant l'étape de génération et\nproduisent en sortie une application complète composée de pages pré-rendues. Par\nexemple, ces outils peuvent récupérer des contenus stockés dans un CMS headless\net générer l'ensemble de votre site sous forme de pages statiques en quelques\nsecondes. Un instantané immuable de toutes ces pages peut alors être déployé en\nproduction et distribué partout dans le monde.\nIl est possible de déclencher des modifications à partir de n'importe quel\nservice en aval grâce à des webhooks et de générer une nouvelle version en\nquelques secondes après avoir récupéré le contenu mis à jour.\nBien que les applications Jamstack soient hébergées de manière statique, cela ne\nveut pas dire qu'elles n'en sont pas moins dynamiques ou interactives que des\npages dont le rendu est effectué côté serveur. Ces applications peuvent être\nconçues pour interagir avec des APIs web côté client et peuvent fournir du\ncontenu dynamique et de l'interactivité comme n'importe quelle application\nclassique. On peut toujours authentifier des utilisateurs et enrichir les pages\nprogressivement en fonctionnalités.\nDes fonctions exécutées depuis le Cloud peuvent fournir des capacités de\nservices additionnelles au besoin, et peuvent être elles aussi être écrites en\nJavaScript.\n\n\n\n\n\n\nExemple d'architecture Jamstack.\n\nPourquoi la Jamstack ?\nUn application Jamstack réduit les dépendances nécessaires lors de l'exécution\npour des performances, une fiabilité, une capacité de montée en charge et une\nsécurité maximales. En effet comme elles n'ont pas besoin de serveurs\nd'application dédiés pour servir les pages, cela réduit considérablement la\nsurface d'attaque possible. Le contenu statique est extrêmement facile à mettre\nen cache et à distribuer via un fournisseur d'accès au Cloud. Vous ne dépendez\npas d'un fournisseur en particulier.\nLes entreprises peuvent développer des solutions à l'aide de la Jamstack pour se\nlancer plus rapidement sur le marché et ce à moindre coût. C'est dû au fait\nqu'il y a besoin de beaucoup moins de ressources pour gérer et maintenir\nl'application en production et en développement. Une petite équipe agile\nmulti-disciplinaire aura seulement besoin d'être en capacité de livrer du HTML,\ndu CSS et du JavaScript. Pas besoin d'avoir beaucoup d'autres compétences quand\nil s'agit de publier pour le web.\nDans les applications traditionnelles, les mises à jour de dépendances se font\ngénéralement sur le serveur. Cela augment le risque d'interruption. Avec la\nJamstack comme tout se fait en aval, il y a beaucoup moins de chances que\nquelque chose puisse impacter les utilisateurs finaux — et encore moins si les\ngénérations sont testées automatiquement avant la mise en production.\nLa JAMStack sur repose sur les services dans le Cloud pour l'extension de\nfonctionnalités. Elle suit la tendance du développement JavaScript full-stack\nfacilité par des outils comme React et les offres de \"functions as a service\" du\nCloud. Fini les serveurs d'applications et les systèmes monolithiques avec des\nbases de données SQL. Plus besoin d'une infrastructure disponible en permanence\npour effectuer du rendu côté serveur à l'aide de langages compilés comme Java ou\n.NET.\nUne architecture Jamstack n'est jamais aussi performante que lorsqu'elle utilise\nles services d'un hébergeur spécialisé comme Netlify. Netlify est une\nplate-forme complète de Cloud pour les architectures Jamstack, qui se connecte\ndirectement à votre dépôt de code. Chaque sauvegarde permet d'à la fois\nversionner et déployer votre application et ses fonctions de support dans le\nCloud sur votre environnement de production en quelques secondes.\nNetlify déploie vos applications sur son réseau global de CDN pour une\nperformance optimale et propose également d'autres fonctionnalités à forte\nvaleur ajoutée, que vous auriez dû autrement développé ou configurer vous-mêmes.\nVerrou logiciel\nBeaucoup d'entreprises utilisent des logiciels tiers qu'elles hébergent\nelles-mêmes, ces logiciels demandent d'avoir des équipes importantes chargées de\nleur développement, de leur déploiement et de leur maintenance, avant même que\nvous n'ayez songé à développeur votre application. Parfois les deux sont\nindissociables et cela augmente considérablement les compétences minimales\nrequises dont vous auriez normalement besoin pour développer votre application.\nLes systèmes de gestion de contenu (CMS) en sont un parfait exemple. Quand ces\nfonctionnalités ne sont pas au coeur de votre activité, aujourd'hui cela n'a\nplus aucun sens d'investir du temps et des ressource spécialisées pour leur\nmaintenance, leur disponibilité et leur support. Ce fut pendant longtemps la\nseule option disponible et un sacrifice à consentir pour essayer de tirer parti\nde la valeur ajoutée que ces outils pouvaient vous procurer en retour.\nDepuis les offres de services ont évolué et les solutions adaptatives du Cloud\nentièrement gérées et maintenues pour une fraction du prix ont changé la donne.\nPrismic et Contentful sont des exemples de solutions de CMS découplés qui\noffrent des APIs web hautement interopérables. Plus besoin des compétences d'un\nexpert maison, ces services s'intègrent facilement à une architecture Jamstack,\nquelle que soit la technologie choisie.\nSur le même principe, presque n'importe quelle fonctionnalité peut être lancée\nou générée par des services offerts par le Cloud. Cela inclut des serices\nd'intégration et de déploiement continus qui peuvent être déclenchés à la\ndemande. Dans la plupart des cas, il n'y a plus besoin d'héberger et de gérer\nvos propres serveurs qu'ils soient chez vous ou dans le Cloud.\nCommodités logicielles\nÉtant donnée que les offres Saas (Software as a service) transforment les\nproduits en services dans le Cloud, les entreprises doivent repenser leur\nstratégie pour rester agiles.\nIl est critique pour une entreprise de comprendre l'offre actuelle et de faire\nappels à ces services distants lorsque c'est possible de façon à réduire ses\ncoûts et éviter de fournir un effort inutile en reproduisant ces infrastructures\ndisponibles partout en interne à un coût plus important.\nLes 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.\nIci le terme commodité fait référence à la technique de cartographie de Wardley 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.\nAvec la Jamstack, on tire parti des services du Cloud comme suit :\n\nhébergement de fichiers statiques plutôt que des serveurs d'application,\nintégration et déploiement dans le Cloud plutôt que chez soi en fonction de ses capacités,\npartenaires Saas plutôt que des produits tiers hébergés en interne,\nfonctions déclenchées à la demande dans le Cloud plutôt que sur des serveurs qui tournent en permanence,\ndes développeurs JavaScript full-stack plutôt que des compétences et des langages variés.\n\nEn résumé\nLes technologies comme Docker sont au coeur des infrastructures modernes. Elles\nsimplifient le provisionnement de ressources de manière très consistante et très\nfiable.\nToutefois, à moins que vous soyez un expert qui fournit des services dans le\nCloud, se focaliser sur l'infrastructure ne fait plus sens.\nUne grande partie de la complexité de l'infrastructure de développement d'un\nproduit interne provient de dépendances lourdes comme celles à des systèmes de\ngestion de contenu. Pendant longtemps il a fallu héberger des logiciels tiers,\nles configurer pour qu'ils soient tout le temps disponibles, et posséder des\ncompétences variées pour développer et maintenir l'ensemble. Cela pousse les\nentreprises à s'engouffrer dans de faux problèmes en se focalisant sur\nl'infrastructure et le déploiement, plutôt que de se défaire de ces dépendances\nqui peuvent s'avérer douloureuses à gérer.\nLa Jamstack propose un modèle d'architecture efficace pour remplacer l'approche\ntraditionnelle du développement web et permet à une petit équipe agile d'adopter\nun bon rythme. Plus besoin de se préoccuper de l'infrastructure et des serveurs.\nElle encourage également l'intégration d'autres offres SaaS qui fournissent des\nAPIs interopérables et s'affranchit des systèmes\nLa Jamstack rend les mises à jour, le support et le redimensionnement de ses\napplications trivial. C'est l'assurance d'une agilité accrue et d'un lancement\nsur le marché plus rapide, avec de meilleures performances. Le monde de\nl'entreprise bouge lentement et ceux qui pourront s'adapter en tireront un\navantage compétitif.",
      "content_html": "<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>\n<p>À l'heure où les entreprises se débattent pour devenir plus agiles et rester\npertinentes, elles peuvent compter sur les dernières évolutions des\ntechnologies. Oubliez Docker, Jamstack marque la prochaine évolution du\ndéveloppement web moderne.</p>\n<p>Jamstack c'est pour JavaScript, APis et Markup. C'est la merveilleuse union de\ntechnologies modernes, du logiciel <em>as a service</em> et des langages au coeur des\nfondations du web. Cette stack procure :</p>\n<ul>\n<li>une réduction des coûts,</li>\n<li>une mise sur le marché plus rapide,</li>\n<li>une sécurité accrue,</li>\n<li>plus de fiabilité, de disponibilité, et une meilleure adaptation à la montée en charge,</li>\n<li>une réduction de la dépendance à des technologies propriétaires.</li>\n</ul>\n<p>Les technologies comme Docker vont continuer à jouer un rôle primordial dans le\nfutur du numérique, mais elles devraient devenir de plus en plus transparentes\npour la majorité des entreprises et seront utilisées sans que vous le sachiez.\nLe fait de devoir gérer et maintenir soi-même sa propre infrastructure\nappartiendra bientôt au passé.</p>\n<p>Si votre coeur de métier n'est pas de fournir des services dans le Cloud, cet\narticle vous explique pourquoi la Jamstack devrait devenir votre première\npréoccupation si vous tenez à fournir un avantage stratégique et une agilité\naccrue à la majorité de vos activités en ligne.</p>\n<h3 id=\"la-jamstack\">La Jamstack ?</h3>\n<p>La Jamstack c'est en partie du JavaScript et du code généré qui peuvent être\nhébergés en statique n'importe où. Elle est parfaitement complémentaire avec les\ntechnologies serverless et peut faire appel à des fonctions serverless exécutées\ndans le Cloud. Les serveurs d'applications traditionnelles sont en passe d'être\ntotalement superflus, en leur lieu et place une bonne partie de la complexité\nest résolue lors de l'étape de génération à l'aide de l'outillage fourni par\nentre autres par JavaScript.</p>\n<p>Des générateurs de site statiques comme Next.js ou Gatsby sont souvent utilisés\npour combler le besoin de faire du rendu côté serveur. Ces outils s'intègrent\nparfaitement avec des services du Cloud pendant l'étape de génération et\nproduisent en sortie une application complète composée de pages pré-rendues. Par\nexemple, ces outils peuvent récupérer des contenus stockés dans un CMS headless\net générer l'ensemble de votre site sous forme de pages statiques en quelques\nsecondes. Un instantané immuable de toutes ces pages peut alors être déployé en\nproduction et distribué partout dans le monde.</p>\n<p>Il est possible de déclencher des modifications à partir de n'importe quel\nservice en aval grâce à des webhooks et de générer une nouvelle version en\nquelques secondes après avoir récupéré le contenu mis à jour.</p>\n<p>Bien que les applications Jamstack soient hébergées de manière statique, cela ne\nveut pas dire qu'elles n'en sont pas moins dynamiques ou interactives que des\npages dont le rendu est effectué côté serveur. Ces applications peuvent être\nconçues pour interagir avec des APIs web côté client et peuvent fournir du\ncontenu dynamique et de l'interactivité comme n'importe quelle application\nclassique. On peut toujours authentifier des utilisateurs et enrichir les pages\nprogressivement en fonctionnalités.</p>\n<p>Des fonctions exécutées depuis le Cloud peuvent fournir des capacités de\nservices additionnelles au besoin, et peuvent être elles aussi être écrites en\nJavaScript.</p>\n<figure>\n<picture title=\"Exemple d&#039;architecture Jamstack.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Exemple d'architecture Jamstack.</figcaption>\n</figure>\n<h3 id=\"pourquoi-la-jamstack\">Pourquoi la Jamstack ?</h3>\n<p>Un application Jamstack réduit les dépendances nécessaires lors de l'exécution\npour des performances, une fiabilité, une capacité de montée en charge et une\nsécurité maximales. En effet comme elles n'ont pas besoin de serveurs\nd'application dédiés pour servir les pages, cela réduit considérablement la\nsurface d'attaque possible. Le contenu statique est extrêmement facile à mettre\nen cache et à distribuer via un fournisseur d'accès au Cloud. Vous ne dépendez\npas d'un fournisseur en particulier.</p>\n<p>Les entreprises peuvent développer des solutions à l'aide de la Jamstack pour se\nlancer plus rapidement sur le marché et ce à moindre coût. C'est dû au fait\nqu'il y a besoin de beaucoup moins de ressources pour gérer et maintenir\nl'application en production et en développement. Une petite équipe agile\nmulti-disciplinaire aura seulement besoin d'être en capacité de livrer du HTML,\ndu CSS et du JavaScript. Pas besoin d'avoir beaucoup d'autres compétences quand\nil s'agit de publier pour le web.</p>\n<p>Dans les applications traditionnelles, les mises à jour de dépendances se font\ngénéralement sur le serveur. Cela augment le risque d'interruption. Avec la\nJamstack comme tout se fait en aval, il y a beaucoup moins de chances que\nquelque chose puisse impacter les utilisateurs finaux — et encore moins si les\ngénérations sont testées automatiquement avant la mise en production.</p>\n<p>La JAMStack sur repose sur les services dans le Cloud pour l'extension de\nfonctionnalités. Elle suit la tendance du développement JavaScript full-stack\nfacilité par des outils comme React et les offres de \"functions as a service\" du\nCloud. Fini les serveurs d'applications et les systèmes monolithiques avec des\nbases de données SQL. Plus besoin d'une infrastructure disponible en permanence\npour effectuer du rendu côté serveur à l'aide de langages compilés comme Java ou\n.NET.</p>\n<p>Une architecture Jamstack n'est jamais aussi performante que lorsqu'elle utilise\nles services d'un hébergeur spécialisé comme Netlify. Netlify est une\nplate-forme complète de Cloud pour les architectures Jamstack, qui se connecte\ndirectement à votre dépôt de code. Chaque sauvegarde permet d'à la fois\nversionner et déployer votre application et ses fonctions de support dans le\nCloud sur votre environnement de production en quelques secondes.</p>\n<p>Netlify déploie vos applications sur son réseau global de CDN pour une\nperformance optimale et propose également d'autres fonctionnalités à forte\nvaleur ajoutée, que vous auriez dû autrement développé ou configurer vous-mêmes.</p>\n<h3 id=\"verrou-logiciel\">Verrou logiciel</h3>\n<p>Beaucoup d'entreprises utilisent des logiciels tiers qu'elles hébergent\nelles-mêmes, ces logiciels demandent d'avoir des équipes importantes chargées de\nleur développement, de leur déploiement et de leur maintenance, avant même que\nvous n'ayez songé à développeur votre application. Parfois les deux sont\nindissociables et cela augmente considérablement les compétences minimales\nrequises dont vous auriez normalement besoin pour développer votre application.</p>\n<p>Les systèmes de gestion de contenu (CMS) en sont un parfait exemple. Quand ces\nfonctionnalités ne sont pas au coeur de votre activité, aujourd'hui cela n'a\nplus aucun sens d'investir du temps et des ressource spécialisées pour leur\nmaintenance, leur disponibilité et leur support. Ce fut pendant longtemps la\nseule option disponible et un sacrifice à consentir pour essayer de tirer parti\nde la valeur ajoutée que ces outils pouvaient vous procurer en retour.</p>\n<p>Depuis les offres de services ont évolué et les solutions adaptatives du Cloud\nentièrement gérées et maintenues pour une fraction du prix ont changé la donne.\nPrismic et Contentful sont des exemples de solutions de CMS découplés qui\noffrent des APIs web hautement interopérables. Plus besoin des compétences d'un\nexpert maison, ces services s'intègrent facilement à une architecture Jamstack,\nquelle que soit la technologie choisie.</p>\n<p>Sur le même principe, presque n'importe quelle fonctionnalité peut être lancée\nou générée par des services offerts par le Cloud. Cela inclut des serices\nd'intégration et de déploiement continus qui peuvent être déclenchés à la\ndemande. Dans la plupart des cas, il n'y a plus besoin d'héberger et de gérer\nvos propres serveurs qu'ils soient chez vous ou dans le Cloud.</p>\n<h3 id=\"commodites-logicielles\">Commodités logicielles</h3>\n<p>Étant donnée que les offres Saas (<em>Software as a service</em>) transforment les\nproduits en services dans le Cloud, les entreprises doivent repenser leur\nstratégie pour rester agiles.</p>\n<p>Il est critique pour une entreprise de comprendre l'offre actuelle et de faire\nappels à ces services distants lorsque c'est possible de façon à réduire ses\ncoûts et éviter de fournir un effort inutile en reproduisant ces infrastructures\ndisponibles partout en interne à un coût plus important.</p>\n<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>\n<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>\n<p>Avec la Jamstack, on tire parti des services du Cloud comme suit :</p>\n<ul>\n<li>hébergement de fichiers statiques plutôt que des serveurs d'application,</li>\n<li>intégration et déploiement dans le Cloud plutôt que chez soi en fonction de ses capacités,</li>\n<li>partenaires Saas plutôt que des produits tiers hébergés en interne,</li>\n<li>fonctions déclenchées à la demande dans le Cloud plutôt que sur des serveurs qui tournent en permanence,</li>\n<li>des développeurs JavaScript full-stack plutôt que des compétences et des langages variés.</li>\n</ul>\n<h3 id=\"en-resume\">En résumé</h3>\n<p>Les technologies comme Docker sont au coeur des infrastructures modernes. Elles\nsimplifient le provisionnement de ressources de manière très consistante et très\nfiable.</p>\n<p>Toutefois, à moins que vous soyez un expert qui fournit des services dans le\nCloud, se focaliser sur l'infrastructure ne fait plus sens.</p>\n<p>Une grande partie de la complexité de l'infrastructure de développement d'un\nproduit interne provient de dépendances lourdes comme celles à des systèmes de\ngestion de contenu. Pendant longtemps il a fallu héberger des logiciels tiers,\nles configurer pour qu'ils soient tout le temps disponibles, et posséder des\ncompétences variées pour développer et maintenir l'ensemble. Cela pousse les\nentreprises à s'engouffrer dans de faux problèmes en se focalisant sur\nl'infrastructure et le déploiement, plutôt que de se défaire de ces dépendances\nqui peuvent s'avérer douloureuses à gérer.</p>\n<p>La Jamstack propose un modèle d'architecture efficace pour remplacer l'approche\ntraditionnelle du développement web et permet à une petit équipe agile d'adopter\nun bon rythme. Plus besoin de se préoccuper de l'infrastructure et des serveurs.\nElle encourage également l'intégration d'autres offres SaaS qui fournissent des\nAPIs interopérables et s'affranchit des systèmes</p>\n<p>La Jamstack rend les mises à jour, le support et le redimensionnement de ses\napplications trivial. C'est l'assurance d'une agilité accrue et d'un lancement\nsur le marché plus rapide, avec de meilleures performances. Le monde de\nl'entreprise bouge lentement et ceux qui pourront s'adapter en tireront un\navantage compétitif.</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/11/28/optimisation-compilation-jekyll/",
      "url": "https://jamstatic.fr/2018/11/28/optimisation-compilation-jekyll/",
      "title": "Optimisation du temps de compilation de Jekyll",
      "summary": "Comment diviser par quinze le temps de génération de son site.",
      "date_published": "2018-11-28T00:00:00+00:00","content_text": "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.\nTrois 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.\nPar 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 build était lent.\nTL;DR\n\nConstatant que ma compilation Jekyll était des plus lentes, j'ai contacté la communauté Jamstack francophone pour des conseils.\nPlusieurs choses ont émergé, chaque conseil permettant d'optimiser le temps de compilation.\nLe plus gros gain provient des évolutions de Jekyll lui-même, sur lequel l'équipe est en train de faire un énorme travail.\n\n\nPour générer mon site (source), j'ai développé un ensemble de commandes Rake1 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 la gemme html-proofer. 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.\nToutes 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.\nIl 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 Netlify2.\nIl était temps d'agir.\n\nUn proverbe africain dit&nbsp;:\n\nSeul, on va plus vite. Ensemble, on va plus loin.\n\nJe ne pense pas que ce soit tout à fait vrai. Je crois que lorsque nous nous entourons des bonnes personnes, nous pouvons aller ailleurs, vers des possibilités que nous n'avions même pas envisagées.\n\nQuand 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 Core Team de Jekyll, Frank. Il m'a beaucoup aidé (et continue de le faire) en me montrant des possibilités que je n'avais pas envisagées.\nInclusions&nbsp;: à consommer avec modération\nL'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.\nDans Jekyll, cela se fait avec des inclusions, via la balise {% include %}. Mais attention, une décomposition de code trop ambitieuse aura un coût sur votre temps de compilation, que vous pourrez visualiser avec le profileur Liquid [EN].\nD'après Pat Hawks, membre de la Core Team Jekyll&nbsp;:\n\nChaque fois que vous utilisez une balise {% include %}, Jekyll doit ouvrir le fichier concerné, lire son contenu en mémoire, puis analyser le gabarit avec Liquid.\nCela se produit à chaque {% include %}, 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…\n\nUne façon de surmonter ce coût supplémentaire est de mettre en cache les blocs compilés pendant l'interprétation de votre {% include %}. Il y a un plugin pour cela&nbsp;: le plugin jekyll-include-cache de Ben Balter. Mais attention à deux choses très importantes&nbsp;:\n\nassurez-vous de passer toutes les données nécessaires à votre {% include %} en paramètres, car elles seront utilisées comme clés pour le cache&nbsp;;\nsi 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.\n\nCes deux contraintes sont si fortes qu'elles m'ont obligé à réintégrer plus de la moitié de mes inclusions dans mes gabarits (_layouts). 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 10&nbsp;% de temps de compilation3 en sacrifiant ce petit confort.\nEt avec des commentaires Liquid ({% comment %}This is a comment{% endcomment %}), je peux toujours organiser efficacement mon code, même réintégré dans un seul fichier.\nFaites confiance aux gemmes compilées en C\nPar 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.\nIl en existe certainement d'autres, mais en voici au moins deux&nbsp;:\n\nla gemme liquid-c, pour optimiser la compilation Liquid&nbsp;;\nla gemme sassc, si vous avez besoin de Jekyll pour compiler des fichiers Sass plus efficacement.\n\nJe n'ai pas besoin de Jekyll pour mes fichiers Sass mais en utilisant liquid-c, j'ai économisé 9&nbsp;% du temps de compilation.\nSi vous pouvez l'éviter, n'utilisez pas LSI\nJekyll est livré avec une option très pratique appelée Latent Semantic Indexing (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 lentement4.\nAprès une très rapide non-analyse des données analytiques dont je ne dispose pas5, j'ai décidé de me séparer de la proposition d'articles associés et d'économiser 17&nbsp;% du temps de compilation.\nMarkdown&nbsp;: choisissez la bonne variante\nMarkdown 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 CommonMark, et les projets l'implémentant fleurissent.\nPar défaut, Jekyll utilise kramdown, un surensemble de Markdown développé en Ruby qui fait du très bon boulot. Pour le remplacer par CommonMark, j'ai voulu utiliser commonmarker qui est, encore une fois, un wrapper Ruby pour une implémentation en C. Pour en tirer parti dans Jekyll, vous pouvez utiliser le plugin jekyll-commonmark de Pat Hawks.\nAttention, 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.\nPar exemple, CommonMark ne supporte pas les attributs de bloc tels que { :.myclass} pour décorer un paragraphe de contenu. Vous aurez besoin d'utiliser de bonnes vieilles balises HTML. N'oubliez pas d'activer l'option UNSAFE dans votre configuration Jekyll (_config.yml) si vous ne voulez pas générer beaucoup de commentaires du type &lt;!-- raw HTML omitted --&gt;&nbsp;:\nmarkdown: CommonMark\ncommonmark:\n  options: [\"SMART\", \"FOOTNOTES\", \"UNSAFE\", \"HARDBREAKS\"]\n  extensions: [\"strikethrough\", \"autolink\", \"table\"]\nVous 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.\nUn petit prix à payer pour gagner encore 9&nbsp;% de temps de compilation.\nFaites confiance à la Team pour aller dans la bonne direction\nEnfin, l'une des dernières améliorations apportées a été le passage à la version master 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;: 93&nbsp;%&nbsp;!\nJe n'arrivais tellement pas à y croire que j'ai temporairement versionné mon dossier _site 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.\nSi vous ne deviez retenir, ou tester, qu'une seule optimisation, c'est celle-ci. Faites confiance à la Core Team, la performance est une de leurs priorités.\nQu'en est-il de mon chantier multilingue&nbsp;?\nAvec 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.\nElle est basée sur une clé front matter i18n-key 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.\nAshwin Maroli, l'un des membres de la Jekyll Plugin Core Team, 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;: jekyll-locale. 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 jekyll-paginate-v2 de Sverrir Sigmundarson, qui me sert pour la pagination.\nJe ne manquerai pas d'en parler quand j'attaquerai à nouveau ce chantier.\n\nProtocole expérimental\nComme 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.\nVoici 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.\nVoici les données brutes, si certains veulent jouer avec&nbsp;:\n\n\n\nRun\nStep\nDone in… (s)\n\n\n\n\nTest 1\n1 - Before\n1337,872\n\n\nTest 1\n2 - Switch to liquid-c\n1509,997\n\n\nTest 1\n3 - Remove LSI\n1264,981\n\n\nTest 1\n4 - Switch to CommonMark\n1282,607\n\n\nTest 1\n5 - Switch to master\n64,949\n\n\nTest 2\n1 - Before\n1510,356\n\n\nTest 2\n2 - Switch to liquid-c\n1457,161\n\n\nTest 2\n3 - Remove LSI\n1239,278\n\n\nTest 2\n4 - Switch to CommonMark\n1058,934\n\n\nTest 2\n5 - Switch to master\n72,335\n\n\nTest 3\n1 - Before\n2148,253\n\n\nTest 3\n2 - Switch to liquid-c\n1465,446\n\n\nTest 3\n3 - Remove LSI\n1253,785\n\n\nTest 3\n4 - Switch to CommonMark\n886,152\n\n\nTest 3\n5 - Switch to master\n92,384\n\n\nTest 4\n1 - Before\n1621,322\n\n\nTest 4\n2 - Switch to liquid-c\n1506,737\n\n\nTest 4\n3 - Remove LSI\n1225,414\n\n\nTest 4\n4 - Switch to CommonMark\n1057,497\n\n\nTest 4\n5 - Switch to master\n87,943\n\n\nTest 5\n1 - Before\n1589,323\n\n\nTest 5\n2 - Switch to liquid-c\n1607,33\n\n\nTest 5\n3 - Remove LSI\n1261,815\n\n\nTest 5\n4 - Switch to CommonMark\n914,124\n\n\nTest 5\n5 - Switch to master\n77,526\n\n\nTest 6\n1 - Before\n1643,596\n\n\nTest 6\n2 - Switch to liquid-c\n1481,98\n\n\nTest 6\n3 - Remove LSI\n1237,843\n\n\nTest 6\n4 - Switch to CommonMark\n1336,47\n\n\nTest 6\n5 - Switch to master\n69,099\n\n\nTest 7\n1 - Before\n1456,432\n\n\nTest 7\n2 - Switch to liquid-c\n1487,558\n\n\nTest 7\n3 - Remove LSI\n1268,737\n\n\nTest 7\n4 - Switch to CommonMark\n1106,233\n\n\nTest 7\n5 - Switch to master\n69,562\n\n\nTest 8\n1 - Before\n2943,453\n\n\nTest 8\n2 - Switch to liquid-c\n1492,383\n\n\nTest 8\n3 - Remove LSI\n1229,34\n\n\nTest 8\n4 - Switch to CommonMark\n1321,156\n\n\nTest 8\n5 - Switch to master\n70,332\n\n\nTest 9\n1 - Before\n1587,532\n\n\nTest 9\n2 - Switch to liquid-c\n1586,698\n\n\nTest 9\n3 - Remove LSI\n1264,424\n\n\nTest 9\n4 - Switch to CommonMark\n950,634\n\n\nTest 9\n5 - Switch to master\n69,907\n\n\nTest 10\n1 - Before\n1643,22\n\n\nTest 10\n2 - Switch to liquid-c\n1581,063\n\n\nTest 10\n3 - Remove LSI\n1229,119\n\n\nTest 10\n4 - Switch to CommonMark\n1332,547\n\n\nTest 10\n5 - Switch to master\n69,022\n\n\n\n\n\n\n\nRake est un orchestrateur similaire à make, mais en Ruby (en savoir plus).&#160;&#8617;\n\n\n15 minutes, comme confirmé par Chris McCraw dans son article \"How Our Build Bots Build Sites\".&#160;&#8617;\n\n\nVous 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;&#8617;\n\n\nIl semblerait que la gemme gsl permette d'améliorer cela, mais je ne l'ai pas testé. Vos retours m'intéressent.&#160;&#8617;\n\n\nParce 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;&#8617;\n\n\n",
      "content_html": "<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.\nTrois 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.\nPar 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>\n<h2 id=\"tl-dr\">TL;DR</h2>\n<ul>\n<li>Constatant que ma compilation Jekyll était des plus lentes, j'ai contacté la communauté Jamstack francophone pour des conseils.</li>\n<li>Plusieurs choses ont émergé, chaque conseil permettant d'optimiser le temps de compilation.</li>\n<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>\n</ul>\n<hr>\n<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>\n<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>\n<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>\n<p>Il était temps d'agir.</p>\n<hr>\n<p>Un proverbe africain dit&nbsp;:</p>\n<blockquote>\n<p>Seul, on va plus vite. Ensemble, on va plus loin.</p>\n</blockquote>\n<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>\n<hr>\n<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>\n<h2 id=\"inclusions-nbsp-a-consommer-avec-moderation\">Inclusions&nbsp;: à consommer avec modération</h2>\n<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>\n<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>\n<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>\n<blockquote>\n<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.\nCela 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>\n</blockquote>\n<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>\n<ol>\n<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>\n<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>\n</ol>\n<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>\n<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>\n<h2 id=\"faites-confiance-aux-gemmes-compilees-en-c\">Faites confiance aux gemmes compilées en C</h2>\n<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>\n<p>Il en existe certainement d'autres, mais en voici au moins deux&nbsp;:</p>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<h2 id=\"si-vous-pouvez-l-eviter-n-utilisez-pas-lsi\">Si vous pouvez l'éviter, n'utilisez pas LSI</h2>\n<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>\n<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>\n<h2 id=\"markdown-nbsp-choisissez-la-bonne-variante\">Markdown&nbsp;: choisissez la bonne variante</h2>\n<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>\n<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>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">markdown:</span> <span class=\"hljs-string\">CommonMark</span>\n<span class=\"hljs-attr\">commonmark:</span>\n  <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>\n  <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>\n<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>\n<p>Un petit prix à payer pour gagner encore <strong>9&nbsp;%</strong> de temps de compilation.</p>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"qu-en-est-il-de-mon-chantier-multilingue-nbsp\">Qu'en est-il de mon chantier multilingue&nbsp;?</h2>\n<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>\n<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>\n<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>\n<p>Je ne manquerai pas d'en parler quand j'attaquerai à nouveau ce chantier.</p>\n<hr>\n<h2 id=\"protocole-experimental\">Protocole expérimental</h2>\n<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>\n<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>\n<p>Voici les données brutes, si certains veulent jouer avec&nbsp;:</p>\n<table>\n<thead>\n<tr>\n<th>Run</th>\n<th>Step</th>\n<th>Done in… (s)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Test 1</td>\n<td>1 - Before</td>\n<td>1337,872</td>\n</tr>\n<tr>\n<td>Test 1</td>\n<td>2 - Switch to liquid-c</td>\n<td>1509,997</td>\n</tr>\n<tr>\n<td>Test 1</td>\n<td>3 - Remove LSI</td>\n<td>1264,981</td>\n</tr>\n<tr>\n<td>Test 1</td>\n<td>4 - Switch to CommonMark</td>\n<td>1282,607</td>\n</tr>\n<tr>\n<td>Test 1</td>\n<td>5 - Switch to master</td>\n<td>64,949</td>\n</tr>\n<tr>\n<td>Test 2</td>\n<td>1 - Before</td>\n<td>1510,356</td>\n</tr>\n<tr>\n<td>Test 2</td>\n<td>2 - Switch to liquid-c</td>\n<td>1457,161</td>\n</tr>\n<tr>\n<td>Test 2</td>\n<td>3 - Remove LSI</td>\n<td>1239,278</td>\n</tr>\n<tr>\n<td>Test 2</td>\n<td>4 - Switch to CommonMark</td>\n<td>1058,934</td>\n</tr>\n<tr>\n<td>Test 2</td>\n<td>5 - Switch to master</td>\n<td>72,335</td>\n</tr>\n<tr>\n<td>Test 3</td>\n<td>1 - Before</td>\n<td>2148,253</td>\n</tr>\n<tr>\n<td>Test 3</td>\n<td>2 - Switch to liquid-c</td>\n<td>1465,446</td>\n</tr>\n<tr>\n<td>Test 3</td>\n<td>3 - Remove LSI</td>\n<td>1253,785</td>\n</tr>\n<tr>\n<td>Test 3</td>\n<td>4 - Switch to CommonMark</td>\n<td>886,152</td>\n</tr>\n<tr>\n<td>Test 3</td>\n<td>5 - Switch to master</td>\n<td>92,384</td>\n</tr>\n<tr>\n<td>Test 4</td>\n<td>1 - Before</td>\n<td>1621,322</td>\n</tr>\n<tr>\n<td>Test 4</td>\n<td>2 - Switch to liquid-c</td>\n<td>1506,737</td>\n</tr>\n<tr>\n<td>Test 4</td>\n<td>3 - Remove LSI</td>\n<td>1225,414</td>\n</tr>\n<tr>\n<td>Test 4</td>\n<td>4 - Switch to CommonMark</td>\n<td>1057,497</td>\n</tr>\n<tr>\n<td>Test 4</td>\n<td>5 - Switch to master</td>\n<td>87,943</td>\n</tr>\n<tr>\n<td>Test 5</td>\n<td>1 - Before</td>\n<td>1589,323</td>\n</tr>\n<tr>\n<td>Test 5</td>\n<td>2 - Switch to liquid-c</td>\n<td>1607,33</td>\n</tr>\n<tr>\n<td>Test 5</td>\n<td>3 - Remove LSI</td>\n<td>1261,815</td>\n</tr>\n<tr>\n<td>Test 5</td>\n<td>4 - Switch to CommonMark</td>\n<td>914,124</td>\n</tr>\n<tr>\n<td>Test 5</td>\n<td>5 - Switch to master</td>\n<td>77,526</td>\n</tr>\n<tr>\n<td>Test 6</td>\n<td>1 - Before</td>\n<td>1643,596</td>\n</tr>\n<tr>\n<td>Test 6</td>\n<td>2 - Switch to liquid-c</td>\n<td>1481,98</td>\n</tr>\n<tr>\n<td>Test 6</td>\n<td>3 - Remove LSI</td>\n<td>1237,843</td>\n</tr>\n<tr>\n<td>Test 6</td>\n<td>4 - Switch to CommonMark</td>\n<td>1336,47</td>\n</tr>\n<tr>\n<td>Test 6</td>\n<td>5 - Switch to master</td>\n<td>69,099</td>\n</tr>\n<tr>\n<td>Test 7</td>\n<td>1 - Before</td>\n<td>1456,432</td>\n</tr>\n<tr>\n<td>Test 7</td>\n<td>2 - Switch to liquid-c</td>\n<td>1487,558</td>\n</tr>\n<tr>\n<td>Test 7</td>\n<td>3 - Remove LSI</td>\n<td>1268,737</td>\n</tr>\n<tr>\n<td>Test 7</td>\n<td>4 - Switch to CommonMark</td>\n<td>1106,233</td>\n</tr>\n<tr>\n<td>Test 7</td>\n<td>5 - Switch to master</td>\n<td>69,562</td>\n</tr>\n<tr>\n<td>Test 8</td>\n<td>1 - Before</td>\n<td>2943,453</td>\n</tr>\n<tr>\n<td>Test 8</td>\n<td>2 - Switch to liquid-c</td>\n<td>1492,383</td>\n</tr>\n<tr>\n<td>Test 8</td>\n<td>3 - Remove LSI</td>\n<td>1229,34</td>\n</tr>\n<tr>\n<td>Test 8</td>\n<td>4 - Switch to CommonMark</td>\n<td>1321,156</td>\n</tr>\n<tr>\n<td>Test 8</td>\n<td>5 - Switch to master</td>\n<td>70,332</td>\n</tr>\n<tr>\n<td>Test 9</td>\n<td>1 - Before</td>\n<td>1587,532</td>\n</tr>\n<tr>\n<td>Test 9</td>\n<td>2 - Switch to liquid-c</td>\n<td>1586,698</td>\n</tr>\n<tr>\n<td>Test 9</td>\n<td>3 - Remove LSI</td>\n<td>1264,424</td>\n</tr>\n<tr>\n<td>Test 9</td>\n<td>4 - Switch to CommonMark</td>\n<td>950,634</td>\n</tr>\n<tr>\n<td>Test 9</td>\n<td>5 - Switch to master</td>\n<td>69,907</td>\n</tr>\n<tr>\n<td>Test 10</td>\n<td>1 - Before</td>\n<td>1643,22</td>\n</tr>\n<tr>\n<td>Test 10</td>\n<td>2 - Switch to liquid-c</td>\n<td>1581,063</td>\n</tr>\n<tr>\n<td>Test 10</td>\n<td>3 - Remove LSI</td>\n<td>1229,119</td>\n</tr>\n<tr>\n<td>Test 10</td>\n<td>4 - Switch to CommonMark</td>\n<td>1332,547</td>\n</tr>\n<tr>\n<td>Test 10</td>\n<td>5 - Switch to master</td>\n<td>69,022</td>\n</tr>\n</tbody>\n</table>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:rake\">\n<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>\n</li>\n<li id=\"fn:netlimit\">\n<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>\n</li>\n<li id=\"fn:parole\">\n<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>\n</li>\n<li id=\"fn:gsl\">\n<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>\n</li>\n<li id=\"fn:analytics\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "boris"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/09/07/microbloguer-avec-jekyll/",
      "url": "https://jamstatic.fr/2018/09/07/microbloguer-avec-jekyll/",
      "title": "Microbloguer avec Jekyll",
      "summary": "Un microblog est un blog qui publie des articles courts, le plus souvent sans titre. Rejoignez la communauté IndieWeb et configurez votre site pour pour publier vos notes pour ensuite les envoyer sur Twitter.",
      "date_published": "2018-09-07T14:26:04+00:00","content_text": "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.\nEt 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 ! 🎉\nUn 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'IndieWeb microbloguent sur leurs sites web. À mon tour, j'ai décidé de faire de même et de configurer mon site pour pouvoir microbloguer.\nJe peux créer deux types de posts, des notes et des articles, ma page d'accueil 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.\nPourquoi 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.\nPuisque 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.\nVoici comment j'ai configuré mon site pour faire ça:\nConfiguration de Jekyll\nJ'ai commencé par utiliser le thème Lanyon 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 comme indiqué dans la documentation.\nVoici à 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.\n---\nlayout: post\ndate: \"2018-03-25T00:05:43.780Z\"\ntitle: \"\"\nslug: \"343\"\n---\n\nJe 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 !\nJ'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 articles\/_posts soit notes\/_posts. On peut créer des pages pour les notes et les articles en se basant sur cet exemple de la documentation de Jekyll.\nMa 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.\n{%- if post.title != \"\" -%}\n  &lt;h3&gt;\n    &lt;a class=\"post-link\" href=\"{{ post.url | relative_url }}\"&gt;\n      {{ post.title | escape }}\n    &lt;\/a&gt;\n  &lt;\/h3&gt;\n{%- endif -%}\nSi 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.\n{%- if post.categories contains 'articles' -%}\n  {{ post.excerpt }}\n  {% if post.content contains site.excerpt_separator %}\n    &lt;a href=\"{{ post.url | prepend: site.baseurl }}\"&gt;Read more&lt;\/a&gt;\n  {% endif %}\n{% else %}\n  {{ post.content }}\n{%- endif -%}\nLa ligne if post.content contains site.excerpt_separator 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.\nPOSSE avec Micro.blog\nPOSSE veut dire Publish (on your) Own Site, Syndicate Elsewhere (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 Micro.blog qui ressemble à Twitter mais en plus petit et en plus sympa.\nAvec 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.Voilà à quoi ressemble mon flux.\nJ'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.\nMicro.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.\nMicro.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.\nPublication\nMicro.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.\nComme 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 : webpage-micropub-to-github.\nC'est une application qui crée un point d'accès Micropub pour les sites Jekyll hébergés sur GitHub Pages.\nJ'ai dû d'abord ajouter quelques lignes de code dans la balise &lt;head&gt; pour l'authentification :\n&lt;link rel=\"authorization_endpoint\" href=\"https:\/\/indieauth.com\/auth\" \/&gt;\n&lt;link rel=\"token_endpoint\" href=\"https:\/\/tokens.indieauth.com\/token\" \/&gt;\n&lt;link href=\"https:\/\/twitter.com\/fionajvoss\" rel=\"me\" \/&gt;\n&lt;link href=\"https:\/\/github.com\/FionaVoss\" rel=\"me\" \/&gt;\nLes deux premières lignes indiquent à Micropub quel service d'authentification\nutiliser, les deux ligne suivantes lient mes profils sociaux à mon site web de\nmanière à ce que je puisse m'authentifier avec IndieAuth.com.\nL'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.\nwebpage-micropub-to-github 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 README 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.\nPetite précision : pour configurer la variable optionnelle MICROPUB_LAYOUT_NAME j'ai du entré la valeur \"posts\", entre guillemets; sans quoi ça faisait planter l'application. Même chose pour MICROPUB_FILENAME_STYLE, que j'ai défini à \"notes\/_posts\/:year-:month-:day-:slug\" pour que mes posts aillent dans la catégorie Notes.\nUne fois déployée, j'ai dû lier l'application dans ma balise &lt;head&gt; pour indiquer à micropub où poster:\n&lt;link\n  rel=\"micropub\"\n  href=\"https:\/\/jekyll-micropub-to-github.herokuapp.com\/micropub\/main\"\n\/&gt;\nEnfin, 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.\nQuand je poste, Micro.blog envoie une requête à webpage-micropub-to-github. Puis webpage-micropub-to-github 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 master, le post apparaît instantanément.\nPasser 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 Quill. 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.\nMê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.",
      "content_html": "<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.\nEt 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>\n<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>\n<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>\n<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>\n<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>\n<p>Voici comment j'ai configuré mon site pour faire ça:</p>\n<h2 id=\"configuration-de-jekyll\">Configuration de Jekyll</h2>\n<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>\n<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>\n<pre><code class=\"language-md hljs markdown\">---\nlayout: post\ndate: \"2018-03-25T00:05:43.780Z\"\ntitle: \"\"\n<span class=\"hljs-section\">slug: \"343\"\n---</span>\n\nJe 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>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{%- <span class=\"hljs-keyword\">if</span> post.title != <span class=\"hljs-string\">\"\"</span> -%}\n  &lt;h3&gt;\n    &lt;a class=<span class=\"hljs-string\">\"post-link\"</span> href=<span class=\"hljs-string\">\"{{ post.url | relative_url }}\"</span>&gt;\n      {{ post.title | escape }}\n    &lt;/a&gt;\n  &lt;/h3&gt;\n{%- endif -%}</code></pre>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{%- <span class=\"hljs-keyword\">if</span> post.categories contains <span class=\"hljs-string\">'articles'</span> -%}\n  {{ post.excerpt }}\n  {% <span class=\"hljs-keyword\">if</span> post.content contains site.excerpt_separator %}\n    &lt;a href=<span class=\"hljs-string\">\"{{ post.url | prepend: site.baseurl }}\"</span>&gt;Read more&lt;/a&gt;\n  {% endif %}\n{% <span class=\"hljs-keyword\">else</span> %}\n  {{ post.content }}\n{%- endif -%}</code></pre>\n<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>\n<h2 id=\"posse-avec-micro-blog\">POSSE avec Micro.blog</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"publication\">Publication</h2>\n<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>\n<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>.\nC'est une application qui crée un point d'accès Micropub pour les sites Jekyll hébergés sur GitHub Pages.</p>\n<p>J'ai dû d'abord ajouter quelques lignes de code dans la balise <code>&lt;head&gt;</code> pour l'authentification :</p>\n<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>\n<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>\n<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>\n<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>\n<p>Les deux premières lignes indiquent à Micropub quel service d'authentification\nutiliser, les deux ligne suivantes lient mes profils sociaux à mon site web de\nmanière à ce que je puisse m'authentifier avec <a href=\"https://indieauth.com/\" target=\"_blank\" rel=\"noopener noreferrer\">IndieAuth.com</a>.</p>\n<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>\n<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>\n<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>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link</span>\n  <span class=\"hljs-attr\">rel</span>=<span class=\"hljs-string\">\"micropub\"</span>\n  <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"https://jekyll-micropub-to-github.herokuapp.com/micropub/main\"</span>\n/&gt;</span></code></pre>\n<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>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/08/26/recherche-plate-forme-de-deploiement-continu-parfaite/",
      "url": "https://jamstatic.fr/2018/08/26/recherche-plate-forme-de-deploiement-continu-parfaite/",
      "title": "À la recherche de la plate-forme de déploiement continu parfaite",
      "summary": "Travis, Circle, Drone, GitLab, Jenkins : choisissez la solution d'intégration et de déploiement continu qui vous convient le mieux.",
      "date_published": "2018-08-26T16:26:33+00:00","content_text": "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.\nNous vous avons déjà parlé des avantages du déploiement automatique, 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.\nLa configuration est clé\nLors de l'évaluation de l'utilité d'un outil de CI\/CD, je veux d'abord connaître la réponse à deux questions :\nQuel niveau de contrôle ai-je sur l'environnement qui va générer une nouvelle version de mon application ? 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é.\nComment se configurent les étapes de build ?\nVous pourriez simplement lancer vos tâches à l'aide d'un gros script shell ou d'un outil robuste comme make. 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.\nPeu 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.\nCircleCI\n\n\n\n\n\nCircleCI 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.\nLa configuration se fait à l'aide d'un fichier .circleci\/config.yml 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.\nCircleCI 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.\nOn aime\nLe fait que ce soit à la fois hautement configurable et simple à intégrer avec les projets GitHub.\nOn est pas super fan\nOn ne peut pas connecter de projets GitLab à CircleCI pour le moment.\nLire comment déployer un site statique avec CircleCI.\nTravisCI\n\n\n\n\n\nTravisCI 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 .travis.yml présent dans votre dépôt.\nLes 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 différents langages de programmation dans votre environnement de build.\nOn aime\nTravisCI 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 lorsqu'une pull request est ouverte.\nOn est pas super fan\nAvec 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.\nGuide de démarrage TravisCI\nDrone\nDrone 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.\nConfigurer un build pour Drone se fait à l'aide d'un fichier .drone.yml. On notera que la syntaxe de configuration de Drone est dérivée de la configuration de docker-compose. Si vous connaissez déjà docker-compose, vous serez en terrain connu avec le langage de configuration de Drone.\nOn aime\nDrone propose des matrices de builds pour permettre de tester simplement votre code dans de multiples configurations, par exemple différentes versions de vos dépendances.\nOn est pas super fan\nDrone est un arrivant relativement récent, et sa documentation aurait besoin d'un peu d'amour.\nBien démarrer avec Drone CI\nGitLab CI\n\n\n\n\n\nSi 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 .gitlab-ci.yml. 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.\nOn aime\nGitLab 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 lancer vos tests d'intégration en local sur votre machine !.\nOn est pas super fan\nForcé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.\nGitLab CI : guide de démarrage rapide\nJenkins\n\nJenkins est un serveur d'intégration et de déploiement continu que vous installez et lancer sur votre propre serveur. Le projet Jenkins a débuté en 2004 et aujourd'hui c'est une solution adoptée par les entreprises qui souhaitent posséder leur propre infrastructure de CI.\nJenkins dispose d'une foule de plugins pour l'ajout de fonctionnalités à votre serveur de CI. Les builds sont configurés dans un fichier Jenkinsfile, à partir du moment où vous avez installé le plugin Pipeline recommandé. Comme CircleCI, vous pouvez configurer votre environnement de build à l'aide d'une image Docker, même s'il existe également beaucoup d'autres options.\nJenkins 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.\nOn aime\nJenkins est totalement libre d'utilisation et open source. Jenkins supporte les plugins et dispose d'une bibliothèque très fournie de par sa relative longévité dans le domaine de l'intégration continue.\nLe 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.\nOn est pas super fan\nPas 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.\nJenkins : Guide de démarrage\nChoisir l'outil qui vous convient le mieux\nLa 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 TravisCI. Les utilisateurs de GitLab devraient se pencher sur l'utilisation de GitLab CI. 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 docker-compose. CircleCI est un bon choix pour ceux qui veulent de la souplesse mais qui ne souhaitent pas héberger leur serveur. Jenkins demandera quelques heures et de l'huile de coude, mais c'est un logiciel capable de faire beaucoup de choses.",
      "content_html": "<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>\n<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>\n<h2 id=\"la-configuration-est-cle\">La configuration est clé</h2>\n<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>\n<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>\n<p><strong>Comment se configurent les étapes de build ?</strong>\nVous 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>\n<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>\n<h2 id=\"circleci\">CircleCI</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<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>\n<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>\n<h3 id=\"on-aime\">On aime</h3>\n<p>Le fait que ce soit à la fois hautement configurable et simple à intégrer avec les projets GitHub.</p>\n<h3 id=\"on-est-pas-super-fan\">On est pas super fan</h3>\n<p>On ne peut pas connecter de projets GitLab à CircleCI pour le moment.</p>\n<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>\n<h2 id=\"travisci\">TravisCI</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<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>\n<h3 id=\"on-aime-1\">On aime</h3>\n<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>\n<h3 id=\"on-est-pas-super-fan-1\">On est pas super fan</h3>\n<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>\n<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>\n<h2 id=\"drone\">Drone</h2>\n<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>\n<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>\n<h3 id=\"on-aime-2\">On aime</h3>\n<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>\n<h3 id=\"on-est-pas-super-fan-2\">On est pas super fan</h3>\n<p>Drone est un arrivant relativement récent, et sa documentation aurait besoin d'un peu d'amour.</p>\n<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>\n<h2 id=\"gitlab-ci\">GitLab CI</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<h3 id=\"on-aime-3\">On aime</h3>\n<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>\n<h3 id=\"on-est-pas-super-fan-3\">On est pas super fan</h3>\n<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>\n<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>\n<h2 id=\"jenkins\">Jenkins</h2>\n<img src=\"/images/jenkins-logo.3e83aa52a384b4cc1de13db61db99231.svg\" alt=\"Jenkins logo\" loading=\"lazy\" decoding=\"async\" class=\"dark:brightness-90\">\n<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>\n<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>\n<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>\n<h3 id=\"on-aime-4\">On aime</h3>\n<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>\n<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>\n<h3 id=\"on-est-pas-super-fan-4\">On est pas super fan</h3>\n<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>\n<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>\n<h2 id=\"choisir-l-outil-qui-vous-convient-le-mieux\">Choisir l'outil qui vous convient le mieux</h2>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/08/17/contenu-multilingue-avec-hugo/",
      "url": "https://jamstatic.fr/2018/08/17/contenu-multilingue-avec-hugo/",
      "title": "Contenu multilingue avec Hugo",
      "summary": "Comment gérer les traductions dans plusieurs langues avec Hugo.",
      "date_published": "2018-08-17T09:36:38+00:00","content_text": "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.\nVoyons ensemble comment configurer un projet Hugo multilingue et traduire votre contenu.\nConfigurer les langues\nLa première chose à faire sur un projet multilingue est d'indiquer à Hugo les langues à prendre en compte. Dans notre exemple nous en aurons trois :\n\nAnglais 🇬🇧\nFrançais 🇫🇷\nEspagnol 🇪🇸\n\nNous ajoutons donc les paramètres suivants dans notre fichier de configuration :\n# config.yaml\nlanguages:\n  en:\n    languageName: English\n    weight: 1\n  fr:\n    languageName: Français\n    weight: 2\n  es:\n    languageName: Español\n    weight: 3\nCes langues sont dorénavant accessibles via .Site.Languages, triées par le poids indiqué, la valeur la plus petite sera la plus importante.\nIl est possible de personnaliser des paramètres globaux déjà définis dans .Site.Params ou .Param. Aucun souci à se faire donc pour le paramètre à appeler.\n# config.yaml\nparams:\n  description: Everything you need to know about the three languages.\n  twitter_handle: 3Languages\n\nlanguages:\n  en:\n    languageName: English\n    weight: 1\n  fr:\n    languageName: Français\n    weight: 2\n    description: Tous ce que vous avez toujours voulu savoir sur les trois langues.\n    twitter_handle: 3Languages_france\n  es:\n    languageName: Español\n    weight: 3\n    description: Todo lo que necesitas saber sobre los tres idiomas.\n    twitter_handle: 3Languages_espana\n\n&lt;meta name=\"description\" content=\"{{ .Param \"description\" }}\"&gt;\n&lt;meta name=\"twitter:site\" content=\"{{ .Param \"twitter_handle\" }}\"&gt;\nTraduire nos pages\nHugo propose deux manière de faire pour traduire vos contenus.\nLa première consiste à inclure le code de la langue dans le nom du fichier de votre contenu, par exemple : \/content\/about.fr.md.\nLa deuxième suppose de créer un fichier dans un dossier dédié à une langue, par exemple : \/content\/french\/about.md.\nNous allons voir en détail comment s'assurer de deux choses, quelle que soit la méthode utilisée :\n\nChaque page a une langue de définie.\nChaque page est reliée à ses traductions.\n\nGérer les traductions avec les noms de fichiers 📄\nVoici à quoi ressemble notre page about et ses traductions :\ncontent\n├── about.md\n├── about.es.md\n└── about.fr.md\nHugo assigne ici le français au fichier about.fr.md et la version espagnole au fichier about.es.md. C'est très intuitif.\nQuid du fichier about.md ? Comme aucune langue n'est précisée, il se verra assigné celle par défaut.\nSi vous n'avez pas défini la valeur de DefaultContentLanguage dans votre fichier de configuration, la langue par défaut est l'anglais.\nSi 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 :\n# config.yaml\nDefaultContentLanguage: fr\nGérer les traductions par dossier 📁\nIl 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 contentDir dans la configuration des langues :\nlanguages:\n  en:\n    languageName: English\n    weight: 1\n    contentDir: content\/english\n  fr:\n    languageName: Français\n    weight: 2\n    contentDir: content\/french\n  es:\n    languageName: Español\n    weight: 3\n    contentDir: content\/spanish\nVous 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.\nEn reprenant l'exemple précédent, notre arborescence de contenus ressemble maintenant à quelque chose comme :\ncontent\n├── english\n│   └── about.md\n├── french\n│   └── about.md\n└── spanish\n    └── about.md\nHugo peut désormais assigner une langue à chacune des pages en fonction du dossier dans lequel elles se trouvent.\nCréer des liens vers les traductions 🔗\nLa création de lien vers les traductions est fondamentale.\nEn 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.\nNous avons vu qu'Hugo sait assigner une langue à une page, mais qu'en est-il de la possibilité de lier des traductions entre elles ?\nDans les deux cas, Hugo va se baser sur le nom de fichier et sa localisation par rapport au dossier content. En fonction du système utilisé, on peut utiliser les nomenclatures suivantes :\n\n\n\nPar nom de fichier\n\n\n\n\n\n\ncontent\/about.md\ncontent\/about.fr.md\n✅\n\n\ncontent\/about.fr.md\ncontent\/about.es.md\n✅\n\n\ncontent\/about\/index.md\ncontent\/about\/index.fr.md\n✅\n\n\ncontent\/about.md\ncontent\/a-propos.fr.md\n🚫\n\n\ncontent\/company\/about.md\ncontent\/about.fr.md\n🚫\n\n\n\n\n\n\nPar dossier\n\n\n\n\n\n\ncontent\/english\/about.md\ncontent\/french\/about.md\n✅\n\n\ncontent\/english\/about\/index.md\ncontent\/french\/about\/index.md\n✅\n\n\ncontent\/english\/about.md\ncontent\/french\/a-propos.md\n🚫\n\n\ncontent\/english\/company\/about.md\ncontent\/english\/about.md\n🚫\n\n\n\nNotez 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 translationKey dans le Front Matter aux pages qui partagent le même contenu.\n# Dans les trois pages : about.md, a-propos.fr.md, acerda.es.md\n---\ntranslationKey: about\n---\nGrâce à cette clé de traduction, en l'absence de nomenclature commune, Hugo se fera un plaisir de relier ces pages entre elles.\nAjouter des liens vers les traductions dans les modèles de page\nMaintenant que nos contenus dans différentes langues sont reliés entre eux, comment en tirer parti dans les gabarits de page ?\nHugo stocke les traductions liées dans deux variables de page :\n\n.Translations pour les autres traductions liées à un contenu,\n.AllTranslations pour toutes les traductions liées y compris celle en cours.\n\nLes traductions sont ici également triées en fonction du paramètre Weight défini dans le fichier configuration.\nPour indiquer aux moteurs de recherche qu'il existe des traductions de contenu, il nous suffit d'ajouter le code suivant dans la balise  &lt;head&gt; :\n{{ if .IsTranslated }}\n  {{ range .Translations }}\n  &lt;link rel=\"alternate\" hreflang=\"{{ .Language.Lang }}\" href=\"{{ .Permalink }}\" title=\"{{ .Language.LanguageName }}\"&gt;\n  {{ end }}\n{{ end }}\nSi nous préférons lister toutes les langues, y compris celle de la page en cours il nous suffit de boucler plutôt sur .AllTranslations.\nOn 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 :\n{{ if .IsTranslated }}\n  &lt;nav class=\"LangNav\"&gt;\n  {{ range .Translations }}\n    &lt;a href=\"{{ .Permalink }}\"&gt;{{ .Language.LanguageName }}&lt;\/a&gt;\n  {{ end}}\n  &lt;\/nav&gt;\n{{ end }}\nL'objet .Language 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.\nLes bundles de page\nHugo vous permet de partager des ressources entre traductions et vous laisse aussi la possibilité de traduire une ressource !\nRevenons à nos pages about 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 :\ncontent\n├── english\n│   └── about\n│       ├── index.md\n│   └── header.jpg\n├── español\n│   └── about\n│       └── index.md\n└── french\n    └── about\n        └── index.md\ncontent\n├── english\n│   └── about\n│       ├── index.md\n│   └── header.jpg\n├── spanish\n│   └── about\n│       └── index.md\n└── french\n    └── about\n        └── index.md\nDans cette configuration, toutes les traductions utilisent la ressource de la langue anglaise header.jpg. 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 .Resources, en écrivant par exemple ici .Resources.GetMatch \"header.jpg\". 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.\nC'est bien pratique.\nMais 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 ?\nIl suffit de déposer notre image dans le dossier de la langue espagnole :\ncontent\n├── english\n│   └── about\n│       ├── index.md\n│   └── header.jpg\n├── spanish\n│   └── about\n│       ├── index.md\n│   └── header.jpg ✨\n└── french\n    └── about\n        └── index.md\nC'est tout, Hugo prendra en compte qu'une ressource dédiée pour la version espagnole de notre page about.\nEt pour la version française ? Quelle image va-t-elle utiliser ? Celle de la version espagnole ou celle de la version anglaise ?\nDans 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.\nSachez 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 about ressemblerait à ceci :\ncontent\n└── about\n    ├── index.md\n    ├── index.es.md\n    ├── index.fr.md\n    ├── header.jpg\n    └── header.es.jpg\nComme la fonction .GetMatch teste la valeur .Title 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 : .Resources.GetMatch \"header*.jpg\"\nConfigurer nos URLs\nQu'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 ?\nPar défaut, Hugo va stocker les pages de la langue par défaut à la racine du dossier de destination public et les autres langues dans leurs répertoires respectifs.\nDonc pour un site en anglais par défaut, les URLs de la page about et de ses traductions seront :\n\nabout\/index.html 🇬🇧\nfr\/about\/index.html 🇫🇷\nes\/about\/index.html 🇪🇸\n\nC'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 :\n# about.fr.md\ntitle: À Propos\nslug: a-propos\n# acerda.es.md\ntitle: Acerda\nslug: acerda\nCe qui a pour effet d'avoir des URLs traduites :\n\nfr\/a-propos\/index.html 🇫🇷 👌\nes\/acerda\/index.html 🇪🇸 👌\n\nNous pourrions décider de stocker les pages en anglais dans un répertoire dédié simplement en définissant le paramètre defaultContentLanguageInSubdir à true dans notre fichier config.yaml\nLocalisation des chaînes de caractères\nLa convention pour la traduction des chaînes de caractères avec Hugo ressemble un peu à celle des fichiers .po 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 i18n\/.\nCe dossier peut se trouver à la racine de votre projet ou d'un thème.\n\ni18n\/en.yaml ✅\nthemes\/academic\/i18n\/en.yaml ✅\n\nPour nos trois langues, ça ressemble à quelque chose comme :\n# i18n\/en.yaml 🇬🇧\n- id: hello\n  translation: \"Hello\"\n- id: how_are_you\n  translation: \"How are you doing?\"\n# i18n\/fr.yaml 🇫🇷\n- id: hello\n  translation: \"Bonjour\"\n- id: how_are_you\n  translation: \"Comment allez-vous ?\"\n# i18n\/es.yaml 🇪🇸\n- id: hello\n  translation: \"Hola\"\n- id: how_are_you\n  translation: \"¿Como estas?\"\nComme 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.\nEnsuite dans nos modèles de page, la fonction i18n d'Hugo se charge du reste.\n\nElle va tester si la clé passée en argument existe et retourner la traduction correspondante si elle existe.\nSi la clé n'existe pas pour la langue courante dans le fichier, elle affichera la traduction de la langue par défaut.\nSi la clé n'existe pas pour la langue par défaut, elle retourne une chaîne vide.\n\n&lt;header&gt;\n    {{ i18n \"hello\" }}\n    &lt;hr&gt;\n    {{ i18n \"how_are_you\" }}\n&lt;\/header&gt;\n&lt;!-- \/es\/index.html 🇪🇸 --&gt;\n&lt;header&gt;\n    Hola\n    &lt;hr&gt;\n    ¿Como estas?\n&lt;\/header&gt;\n&lt;!-- \/fr\/index.html 🇫🇷 --&gt;\n&lt;header&gt;\n    Bonjour\n    &lt;hr&gt;\n    Comment allez-vous ?\n&lt;\/header&gt;\nLa fonction i18n a comme alias T. Si taper i18n est trop fatiguant pour vos petits doigts, vous pouvez donc utiliser la syntaxe abrégée : {{ T \"how_are_you\" }}.\nMettre les chaînes au pluriel\nLes 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 ?\nHugo possède bien une fonction pluralize, mais elle ne gère que l'anglais.\nHeureusement pour nous, les chaînes de traduction d'Hugo nous permettent de gérer parfaitement les autres langues.\nAfin 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.\nComment ça marche ? Hé bien, il s'avère que la valeur de notre traduction peut également être une liste de pluriels.\n# i18n\/en.yaml 🇬🇧\n- id: mouse\n  translation:\n    one: Mouse\n    other: Mice\nExcellent, notre chaîne a maintenant un singulier (one) et une autre version (other) qui sera donc notre pluriel.\nRenseignons donc nos autres fichiers :\n# i18n\/es.yaml 🇪🇸\n- id: mouse\n  translation:\n    one: Ratón\n    other: Ratones\n# i18n\/fr.yaml 🇫🇷\n- id: mouse\n  translation:\n    other: Souris\nComme en français le mot souris est invariable au singulier et au pluriel, nous n'avons qu'à renseigner la version générique other.\nLa fonction i18n 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.\n{{ range .Pages }}\n    &lt;h3&gt;{{ $.Title }}&lt;\/h3&gt;\n    {{ with .Params.mice }}\n        {{ i18n \"this_story_has\" }} {{ . }} {{ i18n \"mouse\" . }}.\n    {{ end }}\n    &lt;hr&gt;\n{{ end }}\nImaginons que nous avons deux histoires, la première avec 24 souris et la seconde avec une seule, voici quel serait le HTML compilé :\n&lt;h3&gt;Cinderella&lt;\/h3&gt;\nThis story has 24 Mice.\n&lt;hr&gt;\n&lt;h3&gt;Fantasia&lt;\/h3&gt;\nThis story has 1 Mouse.\n&lt;hr&gt;\nInclure le nombre d'unités dans la traduction\nVous pouvez même ajouter le nombre exact à la traduction de votre chaîne à l'aide de .Count et fusionner l'ensemble dans une seule chaîne de caractère (notez l'utilisation des guillemets) :\n- id: story_mice\n  translation:\n    other: \"This story has {{ .Count }} Mice\"\n    one: This story has only one Mouse\nDorénavant le nombre de souris sera retourné en sortie de la fonction i18n, nous pouvons mettre à jour notre code pour qu'il utilise plutôt cette chaîne unique :\n- {{ i18n \"this_story_has\" }} {{ . }} {{ i18n \"mouse\" . }}\n+ {{ i18n \"story_mice\" . }}\nLe HTML compilé correspondant sera :\n&lt;h3&gt;Cinderella&lt;\/h3&gt;\nThis story has 24 Mice.\n&lt;hr&gt;\n&lt;h3&gt;Fantasia&lt;\/h3&gt;\nThis story has only one Mouse.\n&lt;hr&gt;\nVous pensez peut-être déjà au cas où il n'y a pas de souris quand le total est 0 ?\nComme expliqué plus bas, cela ne sera pas possible 🙅‍♂️.\nInclusion du contexte dans la traduction\nVous pouvez également passer en second paramètre un contexte à la fonction i18n plutôt qu'un entier.\nLà encore cela peut nous éviter de découper nos phrases en plusieurs chaînes de traduction, quand nous avons besoin de plus que de .Count.\n# i18n\/en.yaml\n- id: intro\n  translation:  \"This is the story of {{ .Params.lead }}{{ with .Params.location }} which takes place in {{ . }}{{ end }}\"\n# i18n\/en.yaml\n- id: intro\n  translation:  \"Voici l'histoire de {{ .Params.lead }}{{ with .Params.location }} qui se déroule à {{ . }}{{ end }}\"\nC'est le même principe que le contexte d'un fichier partiel.\n&lt;h3&gt;{{ .Title }}&lt;\/h3&gt;\n&lt;div class=\"intro\"&gt;{{ i18n \"intro\" . }}&lt;\/div&gt;\n&lt;h3&gt;The Great Mouse Detective&lt;\/h3&gt;\n&lt;div class=\"intro\"&gt;This is the story of Basil which takes place in London&lt;\/div&gt;\nLorsque vous passez un contexte en paramètre d'i18n, vous devez garder certaines choses en tête :\n\ni18n 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 one et other.\nSi cette chaîne est appelée à différents endroits, assurez-vous de toujours lui passer le même contexte ou bien utilisez with comme nous l'avons fait ci-dessus, si vous ne voulez pas vous retrouver avec une erreur bien moche du type can't evaluate field.\n\nTraduction des chaînes avec le système de fichier d'Hugo\nRappelez-vous que nos fichiers i18n sont inclus dans le système de fichier global d'Hugo. En conséquence, tous les fichiers en.yaml présents dans l'arborescence de notre projet Hugo seront fusionnés.\nSi une des traductions du thème que nous utilisons ne nous plaît pas, nous n'avons qu'à créer un fichier i18n\/en.yaml à 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.\n# i18n\/en.yaml\n- id: mouse\n  translation:\n    one: Rodent\n    other: Rodents\nC'est tout ! Pour les autres langues, Hugo se basera sur les Souris et les Ratones 🐁 déclarés dans themes\/miceandmen\/i18n\/.\nUn dernier mot sur les singuliers et les pluriels\nL'anglais comme le français, l'espagnol et bien d'autres langues ne connaît que deux formes de pluralisation, c'est soit du singulier soit du pluriel.\nDonc dans Hugo assez logiquement, pour le traitement d'une chaîne en anglais, les seules possibilités  de mettre au pluriel seront one ou other.\nLa version à utiliser est déterminée par ce test tout simple :\nsi l'entier passé en paramètre de i18n == 1 👉 one\nsinon 👉 other\nC'est tout pour la plupart des langues européennes !\nMaintenant, d'autres langues comme le Russe ont des pluriels spécifiques pour few et many, l'arabe a une forme pour zero et une pour two 1\nSi nous pouvons deviner sans mal le nombre correspondant au pluriel de zero ou two, connaître le nombre exact d'éléments correspondants à few ou many en Russe ressemble davantage à un casse-tête.\nHeureusement, nous pouvons nous reposer sur Hugo et go-i18n de Nick Snyder pour nous aider à assembler toutes les pièces du puzzle.\nVoici tous les pluriels supportés pour l'ensemble des langues :\nzero one two few many other\nMais, cela ne veut pas dire pour autant que vous pouvez les utiliser en anglais.\nSi la langue courante est l'anglais, que votre total de souris est nul, et que vous précisez que le pluriel pour zero est This story has no mouse, vous vous retrouverez quand même avec la valeur utilisée pour other : This story has 0 Mice.\nLa valeur zero n'est prise en compte que si la langue courante est l'arabe ou si cette langue supporte un pluriel pour zero.\nConclusion 🏁\nTraduire 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.\nNous 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.\nSi 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 Cendrillon2, faite-le savoir en commentaire.\n\n\n\n\nhttp:\/\/www.unicode.org\/cldr\/charts\/33\/supplemental\/language_plural_rules.html&#160;&#8617;\n\n\nEvidemment, j'ai pris un nombre au pif !&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<p>Voyons ensemble comment configurer un projet Hugo multilingue et traduire votre contenu.</p>\n<h2 id=\"configurer-les-langues\">Configurer les langues</h2>\n<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>\n<ol>\n<li>Anglais 🇬🇧</li>\n<li>Français 🇫🇷</li>\n<li>Espagnol 🇪🇸</li>\n</ol>\n<p>Nous ajoutons donc les paramètres suivants dans notre fichier de configuration :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># config.yaml</span>\n<span class=\"hljs-attr\">languages:</span>\n  <span class=\"hljs-attr\">en:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">English</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">1</span>\n  <span class=\"hljs-attr\">fr:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">Français</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">2</span>\n  <span class=\"hljs-attr\">es:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">Español</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">3</span></code></pre>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># config.yaml</span>\n<span class=\"hljs-attr\">params:</span>\n  <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>\n  <span class=\"hljs-attr\">twitter_handle:</span> <span class=\"hljs-string\">3Languages</span>\n\n<span class=\"hljs-attr\">languages:</span>\n  <span class=\"hljs-attr\">en:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">English</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">1</span>\n  <span class=\"hljs-attr\">fr:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">Français</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">2</span>\n    <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>\n    <span class=\"hljs-attr\">twitter_handle:</span> <span class=\"hljs-string\">3Languages_france</span>\n  <span class=\"hljs-attr\">es:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">Español</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">3</span>\n    <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>\n    <span class=\"hljs-attr\">twitter_handle:</span> <span class=\"hljs-string\">3Languages_espana</span>\n</code></pre>\n<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;\n&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>\n<h2 id=\"traduire-nos-pages\">Traduire nos pages</h2>\n<p>Hugo propose deux manière de faire pour traduire vos contenus.</p>\n<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>\n<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>\n<p>Nous allons voir en détail comment s'assurer de deux choses, quelle que soit la méthode utilisée :</p>\n<ol>\n<li>Chaque page a une langue de définie.</li>\n<li>Chaque page est reliée à ses traductions.</li>\n</ol>\n<h3 id=\"gG-c-rer-les-traductions-avec-les-noms-de-fichiers-rџ\">Gérer les traductions avec les noms de fichiers 📄</h3>\n<p>Voici à quoi ressemble notre page <code>about</code> et ses traductions :</p>\n<pre><code class=\"language-sh hljs bash\">content\n├── about.md\n├── about.es.md\n└── about.fr.md</code></pre>\n<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>\n<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>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># config.yaml</span>\n<span class=\"hljs-attr\">DefaultContentLanguage:</span> <span class=\"hljs-string\">fr</span></code></pre>\n<h3 id=\"gG-c-rer-les-traductions-par-dossier-rџ-Ѓ\">Gérer les traductions par dossier 📁</h3>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">languages:</span>\n  <span class=\"hljs-attr\">en:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">English</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">1</span>\n    <span class=\"hljs-attr\">contentDir:</span> <span class=\"hljs-string\">content/english</span>\n  <span class=\"hljs-attr\">fr:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">Français</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">2</span>\n    <span class=\"hljs-attr\">contentDir:</span> <span class=\"hljs-string\">content/french</span>\n  <span class=\"hljs-attr\">es:</span>\n    <span class=\"hljs-attr\">languageName:</span> <span class=\"hljs-string\">Español</span>\n    <span class=\"hljs-attr\">weight:</span> <span class=\"hljs-number\">3</span>\n    <span class=\"hljs-attr\">contentDir:</span> <span class=\"hljs-string\">content/spanish</span></code></pre>\n<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>\n<p>En reprenant l'exemple précédent, notre arborescence de contenus ressemble maintenant à quelque chose comme :</p>\n<pre><code class=\"language-sh hljs bash\">content\n├── english\n│   └── about.md\n├── french\n│   └── about.md\n└── spanish\n    └── about.md</code></pre>\n<p>Hugo peut désormais assigner une langue à chacune des pages en fonction du dossier dans lequel elles se trouvent.</p>\n<h2 id=\"crG-c-er-des-liens-vers-les-traductions-rџ\">Créer des liens vers les traductions 🔗</h2>\n<p>La création de lien vers les traductions est fondamentale.</p>\n<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>\n<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>\n<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>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Par nom de fichier</th>\n<th></th>\n<th></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\"><code>content/about.md</code></td>\n<td><code>content/about.fr.md</code></td>\n<td>✅</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>content/about.fr.md</code></td>\n<td><code>content/about.es.md</code></td>\n<td>✅</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>content/about/index.md</code></td>\n<td><code>content/about/index.fr.md</code></td>\n<td>✅</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>content/about.md</code></td>\n<td><code>content/a-propos.fr.md</code></td>\n<td>🚫</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>content/company/about.md</code></td>\n<td><code>content/about.fr.md</code></td>\n<td>🚫</td>\n</tr>\n</tbody>\n</table>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">Par dossier</th>\n<th></th>\n<th></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\"><code>content/english/about.md</code></td>\n<td><code>content/french/about.md</code></td>\n<td>✅</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>content/english/about/index.md</code></td>\n<td><code>content/french/about/index.md</code></td>\n<td>✅</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>content/english/about.md</code></td>\n<td><code>content/french/a-propos.md</code></td>\n<td>🚫</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\"><code>content/english/company/about.md</code></td>\n<td><code>content/english/about.md</code></td>\n<td>🚫</td>\n</tr>\n</tbody>\n</table>\n<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>\n<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>\n---\n<span class=\"hljs-section\">translationKey: about\n---</span></code></pre>\n<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>\n<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>\n<p>Maintenant que nos contenus dans différentes langues sont reliés entre eux, comment en tirer parti dans les gabarits de page ?</p>\n<p>Hugo stocke les traductions liées dans deux variables de page :</p>\n<ul>\n<li><code>.Translations</code> pour les autres traductions liées à un contenu,</li>\n<li><code>.AllTranslations</code> pour toutes les traductions liées y compris celle en cours.</li>\n</ul>\n<p>Les traductions sont ici également triées en fonction du paramètre <code>Weight</code> défini dans le fichier configuration.</p>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">if</span> .IsTranslated }}\n  {{ <span class=\"hljs-keyword\">range</span> .Translations }}\n  &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;\n  {{ end }}\n{{ end }}</code></pre>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">if</span> .IsTranslated }}\n  &lt;nav class=<span class=\"hljs-string\">\"LangNav\"</span>&gt;\n  {{ <span class=\"hljs-keyword\">range</span> .Translations }}\n    &lt;a href=<span class=\"hljs-string\">\"{{ .Permalink }}\"</span>&gt;{{ .Language.LanguageName }}&lt;/a&gt;\n  {{ end}}\n  &lt;/nav&gt;\n{{ end }}</code></pre>\n<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>\n<h2 id=\"les-bundles-de-page\">Les bundles de page</h2>\n<p>Hugo vous permet de partager des ressources entre traductions et vous laisse aussi la possibilité de traduire une ressource !</p>\n<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>\n<pre><code class=\"language-sh hljs bash\">content\n├── english\n│   └── about\n│       ├── index.md\n│   └── header.jpg\n├── español\n│   └── about\n│       └── index.md\n└── french\n    └── about\n        └── index.md</code></pre>\n<pre><code class=\"language-sh hljs bash\">content\n├── english\n│   └── about\n│       ├── index.md\n│   └── header.jpg\n├── spanish\n│   └── about\n│       └── index.md\n└── french\n    └── about\n        └── index.md</code></pre>\n<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>\n<p>C'est bien pratique.\nMais 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>\n<p>Il suffit de déposer notre image dans le dossier de la langue espagnole :</p>\n<pre><code class=\"language-sh hljs bash\">content\n├── english\n│   └── about\n│       ├── index.md\n│   └── header.jpg\n├── spanish\n│   └── about\n│       ├── index.md\n│   └── header.jpg ✨\n└── french\n    └── about\n        └── index.md</code></pre>\n<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>\n<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>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">content\n└── about\n    ├── index.md\n    ├── index.es.md\n    ├── index.fr.md\n    ├── header.jpg\n    └── header.es.jpg</code></pre>\n<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>\n<h2 id=\"configurer-nos-urls\">Configurer nos URLs</h2>\n<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>\n<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>\n<p>Donc pour un site en anglais par défaut, les URLs de la page <code>about</code> et de ses traductions seront :</p>\n<ul>\n<li><code>about/index.html</code> 🇬🇧</li>\n<li><code>fr/about/index.html</code> 🇫🇷</li>\n<li><code>es/about/index.html</code> 🇪🇸</li>\n</ul>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># about.fr.md</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">À</span> <span class=\"hljs-string\">Propos</span>\n<span class=\"hljs-attr\">slug:</span> <span class=\"hljs-string\">a-propos</span></code></pre>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># acerda.es.md</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Acerda</span>\n<span class=\"hljs-attr\">slug:</span> <span class=\"hljs-string\">acerda</span></code></pre>\n<p>Ce qui a pour effet d'avoir des URLs traduites :</p>\n<ul>\n<li><code>fr/a-propos/index.html</code> 🇫🇷 👌</li>\n<li><code>es/acerda/index.html</code> 🇪🇸 👌</li>\n</ul>\n<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>\n<h2 id=\"localisation-des-chaines-de-caracteres\">Localisation des chaînes de caractères</h2>\n<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>\n<p>Ce dossier peut se trouver à la racine de votre projet ou d'un thème.</p>\n<ul>\n<li><code>i18n/en.yaml</code> ✅</li>\n<li><code>themes/academic/i18n/en.yaml</code> ✅</li>\n</ul>\n<p>Pour nos trois langues, ça ressemble à quelque chose comme :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/en.yaml 🇬🇧</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">hello</span>\n  <span class=\"hljs-attr\">translation:</span> <span class=\"hljs-string\">\"Hello\"</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">how_are_you</span>\n  <span class=\"hljs-attr\">translation:</span> <span class=\"hljs-string\">\"How are you doing?\"</span></code></pre>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/fr.yaml 🇫🇷</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">hello</span>\n  <span class=\"hljs-attr\">translation:</span> <span class=\"hljs-string\">\"Bonjour\"</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">how_are_you</span>\n  <span class=\"hljs-attr\">translation:</span> <span class=\"hljs-string\">\"Comment allez-vous ?\"</span></code></pre>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/es.yaml 🇪🇸</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">hello</span>\n  <span class=\"hljs-attr\">translation:</span> <span class=\"hljs-string\">\"Hola\"</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">how_are_you</span>\n  <span class=\"hljs-attr\">translation:</span> <span class=\"hljs-string\">\"¿Como estas?\"</span></code></pre>\n<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>\n<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>\n<ol>\n<li>Elle va tester si la clé passée en argument existe et retourner la traduction correspondante si elle existe.</li>\n<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>\n<li>Si la clé n'existe pas pour la langue par défaut, elle retourne une chaîne vide.</li>\n</ol>\n<pre><code class=\"language-go-html-template hljs go\">&lt;header&gt;\n    {{ i18n <span class=\"hljs-string\">\"hello\"</span> }}\n    &lt;hr&gt;\n    {{ i18n <span class=\"hljs-string\">\"how_are_you\"</span> }}\n&lt;/header&gt;</code></pre>\n<pre><code class=\"language-go-html-template hljs go\">&lt;!-- /es/index.html 🇪🇸 --&gt;\n&lt;header&gt;\n    Hola\n    &lt;hr&gt;\n    ¿Como estas?\n&lt;/header&gt;</code></pre>\n<pre><code class=\"language-go-html-template hljs go\">&lt;!-- /fr/index.html 🇫🇷 --&gt;\n&lt;header&gt;\n    Bonjour\n    &lt;hr&gt;\n    Comment allez-vous ?\n&lt;/header&gt;</code></pre>\n<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>\n<h2 id=\"mettre-les-chaines-au-pluriel\">Mettre les chaînes au pluriel</h2>\n<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>\n<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>\n<p>Heureusement pour nous, les chaînes de traduction d'Hugo nous permettent de gérer parfaitement les autres langues.</p>\n<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>\n<p>Comment ça marche ? Hé bien, il s'avère que la valeur de notre traduction peut également être une liste de pluriels.</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/en.yaml 🇬🇧</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">mouse</span>\n  <span class=\"hljs-attr\">translation:</span>\n    <span class=\"hljs-attr\">one:</span> <span class=\"hljs-string\">Mouse</span>\n    <span class=\"hljs-attr\">other:</span> <span class=\"hljs-string\">Mice</span></code></pre>\n<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>\n<p>Renseignons donc nos autres fichiers :</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/es.yaml 🇪🇸</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">mouse</span>\n  <span class=\"hljs-attr\">translation:</span>\n    <span class=\"hljs-attr\">one:</span> <span class=\"hljs-string\">Ratón</span>\n    <span class=\"hljs-attr\">other:</span> <span class=\"hljs-string\">Ratones</span></code></pre>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/fr.yaml 🇫🇷</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">mouse</span>\n  <span class=\"hljs-attr\">translation:</span>\n    <span class=\"hljs-attr\">other:</span> <span class=\"hljs-string\">Souris</span></code></pre>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">range</span> .Pages }}\n    &lt;h3&gt;{{ $.Title }}&lt;/h3&gt;\n    {{ with .Params.mice }}\n        {{ i18n <span class=\"hljs-string\">\"this_story_has\"</span> }} {{ . }} {{ i18n <span class=\"hljs-string\">\"mouse\"</span> . }}.\n    {{ end }}\n    &lt;hr&gt;\n{{ end }}</code></pre>\n<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>\n<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>\nThis story has 24 Mice.\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">hr</span>&gt;</span>\n<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>\nThis story has 1 Mouse.\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">hr</span>&gt;</span></code></pre>\n<h3 id=\"inclure-le-nombre-d-unites-dans-la-traduction\">Inclure le nombre d'unités dans la traduction</h3>\n<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>\n<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>\n  <span class=\"hljs-attr\">translation:</span>\n    <span class=\"hljs-attr\">other:</span> <span class=\"hljs-string\">\"This story has <span class=\"hljs-template-variable\">{{ .Count }}</span> Mice\"</span>\n    <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>\n<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>\n<pre><code class=\"language-diff hljs diff\"><span class=\"hljs-deletion\">- {{ i18n \"this_story_has\" }} {{ . }} {{ i18n \"mouse\" . }}</span>\n<span class=\"hljs-addition\">+ {{ i18n \"story_mice\" . }}</span></code></pre>\n<p>Le HTML compilé correspondant sera :</p>\n<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>\nThis story has 24 Mice.\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">hr</span>&gt;</span>\n<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>\nThis story has only one Mouse.\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">hr</span>&gt;</span></code></pre>\n<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>\nComme <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>\n<h3 id=\"inclusion-du-contexte-dans-la-traduction\">Inclusion du contexte dans la traduction</h3>\n<p>Vous pouvez également passer en second paramètre un contexte à la fonction <code>i18n</code> plutôt qu'un entier.\nLà 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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/en.yaml</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">intro</span>\n  <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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/en.yaml</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">intro</span>\n  <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>\n<p>C'est le même principe que le contexte d'un fichier partiel.</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;h3&gt;{{ .Title }}&lt;/h3&gt;\n&lt;div class=<span class=\"hljs-string\">\"intro\"</span>&gt;{{ i18n <span class=\"hljs-string\">\"intro\"</span> . }}&lt;/div&gt;</code></pre>\n<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>\n<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>\n<p>Lorsque vous passez un contexte en paramètre d'<code>i18n</code>, vous devez garder certaines choses en tête :</p>\n<ol>\n<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>\n<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>\n</ol>\n<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>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-comment\"># i18n/en.yaml</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">mouse</span>\n  <span class=\"hljs-attr\">translation:</span>\n    <span class=\"hljs-attr\">one:</span> <span class=\"hljs-string\">Rodent</span>\n    <span class=\"hljs-attr\">other:</span> <span class=\"hljs-string\">Rodents</span></code></pre>\n<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>\n<h3 id=\"un-dernier-mot-sur-les-singuliers-et-les-pluriels\">Un dernier mot sur les singuliers et les pluriels</h3>\n<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>\n<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>\n<p>La version à utiliser est déterminée par ce test tout simple :</p>\n<p><strong>si</strong> l'entier passé en paramètre de <code>i18n</code> <strong>==</strong> <code>1</code> 👉 <code>one</code><br>\n<strong>sinon</strong> 👉 <code>other</code></p>\n<p>C'est tout pour la plupart des langues européennes !</p>\n<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>\n<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>\n<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>\n<aside class=\"note note-info\"><p>Voici tous les pluriels supportés pour l'ensemble des langues :\n<code>zero</code> <code>one</code> <code>two</code> <code>few</code> <code>many</code> <code>other</code></p></aside>\n<p>Mais, cela ne veut pas dire pour autant que vous pouvez les utiliser en anglais.</p>\n<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>\n<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>\n<h2 id=\"conclusion-rџЏЃ\">Conclusion 🏁</h2>\n<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>\n<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>\n<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>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n<li id=\"fn:2\">\n<p>Evidemment, j'ai pris un nombre au pif !&#160;<a href=\"#fnref1:2\" rev=\"footnote\" class=\"footnote-backref\">&#8617;</a></p>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "regis"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/07/21/hugo-asset-pipeline/",
      "url": "https://jamstatic.fr/2018/07/21/hugo-asset-pipeline/",
      "title": "La gestion des assets avec Hugo",
      "summary": "Depuis la version 0.43 Hugo offre une gestion des assets: Sass, minification, support de postCSS, concaténation des fichiers JS, et plus encore.",
      "date_published": "2018-07-21T18:54:26+00:00","content_text": "Depuis la version 0.43, 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.\nHugo 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 npm, 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 package.json jamais à jour.\nTraitement des assets\n\n\n\n\n\n\nPhoto de Neil Cooper\n\nLe principe est simple : tout ce qui se trouve dans le dossier \/assets (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 assetDir dans votre fichier de configuration.\nOn peut donc par exemple écrire ceci dans un fichier de layout :\n{{ $styles := resources.Get \"scss\/main.scss\" | toCSS | minify }}\n&lt;link rel=\"stylesheet\" href=\"{{ $styles.Permalink }}\" media=\"screen\"&gt;\nIci on récupère le contenu du fichier \/assets\/scss\/main.scss et on lui applique successivement deux fonctions : d'abord toCSS (alias de resources.ToCSS) pour compiler le fichier Sass puis minify (alias de resources.Minify). La syntaxe des pipes 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.\nDifficile de faire plus concis et plus simple vous en conviendrez.\nMaintenant 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 source map pour développer dans votre navigateur web préféré. Qu'à cela ne tienne, grâce à la fonction isServer qui détecte si vous êtes en train de développer localement avec hugo server ou pas, vous pouvez personnaliser ce qui est généré.\nPrenons donc un exemple un peu plus réaliste :\n{{ if .Site.IsServer }}\n  {{ $cssOpts := (dict \"targetPath\" \"assets\/css\/main.css\" \"enableSourceMap\" true) }}\n  {{ $styles := resources.Get \"scss\/main.scss\" | toCSS $cssOpts }}\n  &lt;link rel=\"stylesheet\" href=\"{{ $styles.Permalink }}\" media=\"screen\"&gt;\n{{ else }}\n  {{$cssOpts := (dict \"targetPath\" \"assets\/css\/main.css\") }}\n  {{ $styles := resources.Get \"scss\/main.scss\" | toCSS $cssOpts | postCSS | minify | fingerprint }}\n  &lt;link rel=\"stylesheet\" href=\"{{ $styles.Permalink }}\" media=\"screen\"&gt;\n{{ end }}\nIci, pendant le développement avec hugo server, on stocke d'abord les options à passer à la fonction toCSS 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.\nPuis 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 link.\nLes options de la fonction toCSS 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 source map. Par contre on peut choisir d'appliquer d'autres fonctions à notre fichier CSS, comme par exemple un post-traitement avec postCSS, au hasard autoprefixer 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 postCSS, cela implique d'avoir les packages npm qui vont bien sur votre machine, que ce soit en global ou en local.\nEt ensuite ?\nVous pouvez aller jeter un œil au dépôt d'exemple de l'utilisation de Sass avec Hugo 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 exemple de configuration avec PostCSS, TailwindCSS, PurgeCSS et autoprefixer.\nVous pouvez également placer des images dans le répertoire assets et procéder à des transformations comme c'était déjà possible au niveau des ressources d'une page.\nPour le JS, sachez qu'il existe une fonction pour la concaténation, qui vous permettra de faire vos bundle en fonction des fichiers dont vous avez besoin dans vos layouts.\nJe vous recommande chaudement de lire l'article de Régis Philibert qui détaille l'utilisation des différentes fonctions offertes par Hugo pour la gestion des assets et bien entendu de vous référer à la documentation officielle qui est toujours à jour.\nMine 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.\nOn notera qu'il existe maintenant deux versions des binaires d'Hugo, celle avec le support de la gestion des assets c'est la version extended.\nSi vous utilisez Netlify pour générer automatiquement votre site à chaque commit, sachez que la version extended n'est pas encore disponible à l'heure où j'écris ces lignes.\nLa génération des fichiers CSS et des fichiers JS n'est pas quelque chose que vous avez forcément besoin de lancer à chaque build, ou à chaque fois qu'un contributeur édite un fichier Markdown dans votre headless CMS.\nEn fonction de votre contexte vous pouvez choisir de :\n\ngénérer les assets localement avec hugo et commiter les fichiers générés dans le répertoire resources à chaque fois que vous modifiez le contenu du dossier assets,\nrecopier et commiter la version étendue du ficher binaire d'Hugo dans le répertoire bin de votre dépôt et modifier la commande de build en .\/bin\/hugo au lieu de hugo.\n\nEn 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.\nConclusion\nHugo 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 framework de développement, si l'on en croit sa baseline officielle. Hugo n'en reste pas moins une application monolithique comparée aux autres SSG de l'écosystème npm comme Gatsby ou Next, à vous de voir si l'ensemble de fonctionnalités dont il dispose maintenant suffit pour votre projet.",
      "content_html": "<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>\n<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>\n<h2 id=\"traitement-des-assets\">Traitement des assets</h2>\n<figure>\n<picture title=\"Photo de Neil Cooper\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Photo de <a href=\"https://unsplash.com/photos/KX2fCzuQoaQ\" target=\"_blank\" rel=\"noopener noreferrer\">Neil Cooper</a></figcaption>\n</figure>\n<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>\n<p>On peut donc par exemple écrire ceci dans un fichier de layout :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ $styles := resources.Get <span class=\"hljs-string\">\"scss/main.scss\"</span> | toCSS | minify }}\n&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>\n<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.\nDifficile de faire plus concis et plus simple vous en conviendrez.</p>\n<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>\n<p>Prenons donc un exemple un peu plus réaliste :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">if</span> .Site.IsServer }}\n  {{ $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>) }}\n  {{ $styles := resources.Get <span class=\"hljs-string\">\"scss/main.scss\"</span> | toCSS $cssOpts }}\n  &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;\n{{ <span class=\"hljs-keyword\">else</span> }}\n  {{$cssOpts := (dict <span class=\"hljs-string\">\"targetPath\"</span> <span class=\"hljs-string\">\"assets/css/main.css\"</span>) }}\n  {{ $styles := resources.Get <span class=\"hljs-string\">\"scss/main.scss\"</span> | toCSS $cssOpts | postCSS | minify | fingerprint }}\n  &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;\n{{ end }}</code></pre>\n<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.\nPuis 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>\n<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>\n<h2 id=\"et-ensuite聽\">Et ensuite ?</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<p>En fonction de votre contexte vous pouvez choisir de :</p>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/05/31/startups-jamstack/",
      "url": "https://jamstatic.fr/2018/05/31/startups-jamstack/",
      "title": "Les startups de l’écosystème Jamstack",
      "summary": "Les capital risqueurs de la Silicon Valley misent sur les startups qui forment l'écosytème de la Jamstack.",
      "date_published": "2018-05-31T18:21:08+00:00","content_text": "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.\nDans cet article CRV 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.\nLa 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.\nPourquoi maintenant ?\nL'explosion de JavaScript : 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.\nLa prolifération des APIs : des microservices pour le backend à la popularité grandissante de GraphQL et aux frontends librement couplés, les APIs sont un élément central du changement architectural auquel nous assistons.\nDavantage de développeurs : 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.\nL'essor de Git : les processus basés sur Git sont désormais au cœur de la collaboration entre développeurs et du versionnement de projet.\nLes progrès des infrastructures du Cloud : la réduction des coûts de fonctionnement, les architectures serverless 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.\nQuelle 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\nL'histoire de la Jamstack\nLes 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 Disqus ou Algolia, 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 Netlify, ont diffusé le concept de la Jamstack: une approche différente, serverless 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.\nLe paysage de la Jamstack\nCette 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.\n\n\n\n\n\nLes bénéfices\nIl 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.\nLe 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 Contentful or Tipe) 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.\nEt ensuite ?\nPermettre une meilleure collaboration entre toutes les parties prenantes du frontend — les créateurs de contenus, les gens du marketing, du design et du développement. « 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 » — Scott Moss, CEO de Tipe.\nLa conception d'interface basée sur des composants est en pleine expansion, l'utilisation de Storybook 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 Chroma.\nDes services pour le frontend : des plate-formes comme Vercel 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.\nL'Edge Computing 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.\nConclusion\nLes 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. CRV 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.",
      "content_html": "<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>\n<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>\n<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>\n<h2 id=\"pourquoi-maintenant\">Pourquoi maintenant ?</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"l-histoire-de-la-jamstack\">L'histoire de la Jamstack</h2>\n<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>\n<h2 id=\"le-paysage-de-la-jamstack\">Le paysage de la Jamstack</h2>\n<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>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<h2 id=\"les-benefices\">Les bénéfices</h2>\n<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>\n<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>\n<h2 id=\"et-ensuite\">Et ensuite ?</h2>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"conclusion\">Conclusion</h2>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://strapi.io/blog/building-a-static-website-using-gatsby-and-strapi",
      "url": "https://strapi.io/blog/building-a-static-website-using-gatsby-and-strapi",
      "title": "Développer un blog statique avec Gatsby et Strapi",
      "summary": "Découvrez comment développer un blog en utilisant Gatsby et Strapi en quelques minutes.",
      "date_published": "2018-04-26T00:00:00+00:00","content_text": "Une nouvelle version de ce tutoriel a été publié en Février 2020 sur https:\/\/strapi.io\/blog\/build-a-static-blog-with-gatsby-and-strapi\nIntroduction\nUn 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.\nPour faciliter leur création, de nombreux générateurs de sites statiques sont disponibles: Jekyll, Hugo, Hexo, 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.\nLes Progressive Web Apps (PWA) sont des applications web, reposant fortement sur l’utilisation de JavaScript, qui se veulent être fiables, rapides et engageantes. É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.\n\nGatsby : quand les sites statiques rencontrent les Progressive Web Apps.\n\nLes 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 : Gatsby. 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.\nQu’est-ce que Gatsby ?\nD’après ses créateurs, Gatsby est un \"blazing-fast website framework for React\". 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.\n\n\nLogo Gatsby\n\nÉ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 \"node\" interface, qui est le centre du système de données de Gatsby.\nCréé par Kyle Mathews, le projet a été officiellement publié en juillet 2017 et est d’ores et déjà utilisé par des dizaines d’entreprises.\nQu’est-ce que Strapi?\nStrapi 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.\n\n\nLogo Strapi\n\nGrâ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.\nContrairement aux CMS en ligne, Strapi est 100% open-source, ce qui veut dire que :\n\nStrapi est totalement gratuit\nVous pouvez l’héberger sur vos propres serveurs. Vous êtes donc propriétaire de votre donnée.\nIl est entièrement personnalisable et extensible, grâce au système de plugins.\n\nInstallation de l’API\nTout d’abord, nous allons commencer par créer une API avec Strapi et ajouter du contenu.\nCréation du projet Strapi\nInstallation de Strapi\nPrérequis: vérifiez que Node 8 (ou plus) et MongoDB sont installés et démarrés sur votre machine.\nInstallez Strapi via npm :\nnpm i strapi@alpha -g\nNote : Strapi v3 est encore en version alpha, mais cela ne posera aucun problème pour la réalisation de ce tutoriel.\nCréation d’un projet Strapi\nCréez un dossier nommé gatsby-strapi-tutorial :\nmkdir gatsby-strapi-tutorial\nGénérez l’API au sein de ce nouveau dossier :\ncd gatsby-strapi-tutorial\nstrapi new api\nDémarrage du serveur\nEntrez à l’intérieur du projet généré :\ncd api\nDémarrez le serveur Node.js :\nstrapi start\nÀ partir de maintenant, vous devriez être à même de voir le panel d’administration de votre projet : http:\/\/localhost:1337\/admin.\nCréation du premier utilisateur\nAjoutez votre premier utilisateur depuis la page d’inscription.\n\n\n\n\n\n\nÉcran de création d’un utilisateur\n\nCréation d’un type de contenu\nLes 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).\nCréez un Content Type nommé article contenant trois champs : title (type string), content (type text) et author (type relation: un utilisateur doit pouvoir être lié à plusieurs articles).\n\n\n\n\n\n\nÉcran de définition de relation\n\n\n\n\n\n\n\nÉcran des champs d’un article\n\nAjout d’articles\nAjoutez quelques articles en base de données. Pour cela, suivez les étapes suivantes :\n\nVisitez la page listant les articles.\nCliquer sur Add New Article.\nRenseignez un titre et un contenu, liez l’article à un auteur, puis valider.\nAjouter deux autres articles.\n\n\n\n\n\n\n\nÉcran listant des articles\n\nAutorisation d’accès\nPour des raisons de sécurité, l’accès à l’API est, par défaut, restreint.\nPour autoriser l’accès, visitez la section Auth &amp; Permissions du rôle Guest, sélectionnez l’action Article - find et sauvegardez. À partir de ce moment, vous devriez être à même de requêter la liste d’articles.\nL’accès à l’API des auteurs est également restreint. Autorisez l’accès aux anonymes en sélectionnant l’action Users &amp; Permissions - find puis en sauvegardant.\n\n\n\n\n\n\nÉcran du rôle Guest\n\nDéveloppement du site statique\nBien joué, votre API est prête à l’utilisation ! Nous allons maintenant commencer le développement du site statique.\nInstallation de Gatsby\nCommençez par installer Gatsby :\nnpm install --global gatsby-cli\nCréation d’un projet Gatsby\nDans le dossier gatsby-strapi-tutorial que vous avez précédemment créé, générez votre tout nouveau blog :\ngatsby new blog\nDémarrage en mode développement\nEntrez dans le dossier du projet :\ncd blog\nDémarrez le serveur :\ngatsby develop\nÀ partir de ce moment, votre site Gatsby devrait être disponible à l’adresse suivante : http:\/\/localhost:8000.\nInstallation du plugin source Strapi\nLorsque 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.\nGatsby 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 GraphQL.\nPour connecter Gatsby à une nouvelle source de données, il faut développer un \"plugin source\". Heureusement, de nombreux plugins source sont déjà disponibles. Vous devriez donc réussir à trouver celui qui correspond le mieux à vos besoins.\nDans cet exemple nous utilisons Strapi. Nous allons donc évidemment avoir besoin d’un plugin source dédié au API Strapi. Bonne nouvelle : celui-ci existe déjà !\nInstallons-le :\nnpm install --save gatsby-source-strapi\nLe plugin a besoin d’être configuré. Remplacez le contenu du fichier gatsby-config.js avec :\nPath : gatsby-config.js\nmodule.exports = {\n  siteMetadata: {\n    title: `Gatsby Default Starter`,\n  },\n  plugins: [\n    `gatsby-plugin-react-helmet`,\n    {\n      resolve: `gatsby-source-strapi`,\n      options: {\n        apiURL: `http:\/\/localhost:1337`,\n        contentTypes: [\n          \/\/ List of the Content Types you want to be able to request from Gatsby.\n          `article`,\n          `user`,\n        ],\n      },\n    },\n  ],\n};\nEnsuite, redémarrez le serveur afin que Gatsby prenne en compte ces changements.\nListe d’articles\nDans un premier temps, nous voulons afficher la liste d’articles. Pour cela, remplacez le contenu de la page d’accueil par le suivant :\nPath : src\/pages\/index.js\nimport React from \"react\";\nimport Link from \"gatsby-link\";\n\nconst IndexPage = ({ data }) =&gt; (\n  &lt;div&gt;\n    &lt;h1&gt;Hi people&lt;\/h1&gt;\n    &lt;p&gt;Welcome to your new Gatsby site.&lt;\/p&gt;\n    &lt;p&gt;Now go build something great.&lt;\/p&gt;\n    &lt;ul&gt;\n      {data.allStrapiArticle.edges.map((document) =&gt; (\n        &lt;li key={document.node.id}&gt;\n          &lt;h2&gt;\n            &lt;Link to={`\/${document.node.id}`}&gt;{document.node.title}&lt;\/Link&gt;\n          &lt;\/h2&gt;\n          &lt;p&gt;{document.node.content}&lt;\/p&gt;\n        &lt;\/li&gt;\n      ))}\n    &lt;\/ul&gt;\n    &lt;Link to=\"\/page-2\/\"&gt;Go to page 2&lt;\/Link&gt;\n  &lt;\/div&gt;\n);\n\nexport default IndexPage;\n\nexport const pageQuery = graphql`\n  query IndexQuery {\n    allStrapiArticle {\n      edges {\n        node {\n          id\n          title\n          content\n        }\n      }\n    }\n  }\n`;\nQue venons-nous de faire ?\nÀ la fin du fichier nous exportons pageQuery : une requête GraphQL qui requête la liste entière des articles. Comme vous pouvez le voir, nous requêtons uniquement les champs id, title et content grâce à la précision du langage GraphQL.\nEnsuite, nous passons l’objet déstructuré { data } comme paramètre de IndexPage et nous bouclons sur son objet allStrapiArticles afin d’afficher la donnée.\n\n\n\n\n\n\nExemple d’écran d’accueil\n\nAstuce : générez vos requêtes GraphQL en quelques secondes !\nGatsby inclut l’excellente interface GraphiQL. Cela facilite grandement la création de requêtes GraphQL. Jetez-y un œil et tentez de créer quelques requêtes.\nVue article\nNotre site commence à ressembler à un blog. C’est une bonne nouvelle ! Cependant, une partie importante est encore manquante : la page article.\nCommençons par créer le template contenant la requête GraphQL et définissant le contenu affiché :\nPath : src\/templates\/article.js\nimport React from \"react\";\nimport Link from \"gatsby-link\";\n\nconst ArticleTemplate = ({ data }) =&gt; (\n  &lt;div&gt;\n    &lt;h1&gt;{data.strapiArticle.title}&lt;\/h1&gt;\n    &lt;p&gt;\n      by{\" \"}\n      &lt;Link to={`\/authors\/${data.strapiArticle.author.id}`}&gt;\n        {data.strapiArticle.author.username}\n      &lt;\/Link&gt;\n    &lt;\/p&gt;\n    &lt;p&gt;{data.strapiArticle.content}&lt;\/p&gt;\n  &lt;\/div&gt;\n);\n\nexport default ArticleTemplate;\n\nexport const query = graphql`\n  query ArticleTemplate($id: String!) {\n    strapiArticle(id: { eq: $id }) {\n      title\n      content\n      author {\n        id\n        username\n      }\n    }\n  }\n`;\nTout 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 fonction createPage.\nTout d’abord, nous allons déclarer une nouvelle fonction nommée makeRequest afin d’exécuter la requête GraphQL. Ensuite, nous exportons une fonction nommée createPages dans laquelle nous récupérons la liste d’articles et créons une page pour chacun d’entre eux. Voici le résultat :\nPath : gatsby-node.js\nconst path = require(`path`);\n\nconst makeRequest = (graphql, request) =&gt;\n  new Promise((resolve, reject) =&gt; {\n    \/\/ Query for nodes to use in creating pages.\n    resolve(\n      graphql(request).then((result) =&gt; {\n        if (result.errors) {\n          reject(result.errors);\n        }\n\n        return result;\n      })\n    );\n  });\n\n\/\/ Implement the Gatsby API “createPages”. This is called once the\n\/\/ data layer is bootstrapped to let plugins create pages from data.\nexports.createPages = ({ boundActionCreators, graphql }) =&gt; {\n  const { createPage } = boundActionCreators;\n\n  const getArticles = makeRequest(\n    graphql,\n    `\n    {\n      allStrapiArticle {\n        edges {\n          node {\n            id\n          }\n        }\n      }\n    }\n    `\n  ).then((result) =&gt; {\n    \/\/ Create pages for each article.\n    result.data.allStrapiArticle.edges.forEach(({ node }) =&gt; {\n      createPage({\n        path: `\/${node.id}`,\n        component: path.resolve(`src\/templates\/article.js`),\n        context: {\n          id: node.id,\n        },\n      });\n    });\n  });\n\n  \/\/ Query for articles nodes to use in creating pages.\n  return getArticles;\n};\nRedémarrez le serveur Gatsby.\nÀ partir de maintenant, vous devriez pouvoir visiter les pages de chaque article en cliquant sur les liens affichés sur la page d’accueil.\n\n\n\n\n\n\nExemple de page\n\nVue auteur\nLes articles sont rédigés par des auteurs. Eux-aussi méritent une page dédiée.\nLa création de la page auteur est très similaire à celle de la page article. Premièrement, nous créons le template :\nPath : src\/templates\/user.js\nimport React from \"react\";\nimport Link from \"gatsby-link\";\n\nconst UserTemplate = ({ data }) =&gt; (\n  &lt;div&gt;\n    &lt;h1&gt;{data.strapiUser.username}&lt;\/h1&gt;\n    &lt;ul&gt;\n      {data.strapiUser.articles.map((article) =&gt; (\n        &lt;li key={article.id}&gt;\n          &lt;h2&gt;\n            &lt;Link to={`\/${article.id}`}&gt;{article.title}&lt;\/Link&gt;\n          &lt;\/h2&gt;\n          &lt;p&gt;{article.content}&lt;\/p&gt;\n        &lt;\/li&gt;\n      ))}\n    &lt;\/ul&gt;\n  &lt;\/div&gt;\n);\n\nexport default UserTemplate;\n\nexport const query = graphql`\n  query UserTemplate($id: String!) {\n    strapiUser(id: { eq: $id }) {\n      id\n      username\n      articles {\n        id\n        title\n        content\n      }\n    }\n  }\n`;\nEnsuite, nous mettons à jour le fichier gatsby-node.js pour créer les URLs :\nPath : gatsby-node.js\nconst path = require(`path`);\n\nconst makeRequest = (graphql, request) =&gt;\n  new Promise((resolve, reject) =&gt; {\n    \/\/ Query for article nodes to use in creating pages.\n    resolve(\n      graphql(request).then((result) =&gt; {\n        if (result.errors) {\n          reject(result.errors);\n        }\n\n        return result;\n      })\n    );\n  });\n\n\/\/ Implement the Gatsby API “createPages”. This is called once the\n\/\/ data layer is bootstrapped to let plugins create pages from data.\nexports.createPages = ({ boundActionCreators, graphql }) =&gt; {\n  const { createPage } = boundActionCreators;\n\n  const getArticles = makeRequest(\n    graphql,\n    `\n    {\n      allStrapiArticle {\n        edges {\n          node {\n            id\n          }\n        }\n      }\n    }\n    `\n  ).then((result) =&gt; {\n    \/\/ Create pages for each article.\n    result.data.allStrapiArticle.edges.forEach(({ node }) =&gt; {\n      createPage({\n        path: `\/${node.id}`,\n        component: path.resolve(`src\/templates\/article.js`),\n        context: {\n          id: node.id,\n        },\n      });\n    });\n  });\n\n  const getAuthors = makeRequest(\n    graphql,\n    `\n    {\n      allStrapiUser {\n        edges {\n          node {\n            id\n          }\n        }\n      }\n    }\n    `\n  ).then((result) =&gt; {\n    \/\/ Create pages for each user.\n    result.data.allStrapiUser.edges.forEach(({ node }) =&gt; {\n      createPage({\n        path: `\/authors\/${node.id}`,\n        component: path.resolve(`src\/templates\/user.js`),\n        context: {\n          id: node.id,\n        },\n      });\n    });\n  });\n\n  \/\/ Queries for articles and authors nodes to use in creating pages.\n  return Promise.all([getArticles, getAuthors]);\n};\nDernière étape : redémarrez le serveur et visitez la page auteur depuis la page d’un article.\n\n\n\n\n\n\nExemple de page\n\nConclusion\nFélicitations ! Vous avez créé un blog super rapide et facile à maintenir !\nÉ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.\nQue faire ensuite ?\nN’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.).\nLorsque votre projet sera terminé, vous voudrez probablement le déployer. Le site statique généré par Gatsby peut facilement être publiée sur des services de stockage 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.\nLe code source de ce tutoriel est disponible sur GitHub. Pour le tester, clonez-le repository et suivez les instructions présentes dans le Readme.\nNous 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.",
      "content_html": "<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>\n<h2 id=\"introduction\">Introduction</h2>\n<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>\n<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>\n<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>\n<blockquote>\n<p>Gatsby : quand les sites statiques rencontrent les Progressive Web Apps.</p>\n</blockquote>\n<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>\n<h3 id=\"qu-est-ce-que-gatsby\">Qu’est-ce que Gatsby ?</h3>\n<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>\n<figure>\n<img src=\"/images/gatsby-logo.d8560250fbd350c6ba73def4f636c445.svg\" alt=\"Logo Gatsby\" title=\"Logo Gatsby\" loading=\"lazy\" decoding=\"async\" class=\"dark:brightness-90\">\n<figcaption>Logo Gatsby</figcaption>\n</figure>\n<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>\n<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>\n<h3 id=\"qu-est-ce-que-strapi\">Qu’est-ce que Strapi?</h3>\n<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>\n<figure>\n<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\">\n<figcaption>Logo Strapi</figcaption>\n</figure>\n<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>\n<p>Contrairement aux CMS en ligne, <strong>Strapi est 100% open-source</strong>, ce qui veut dire que :</p>\n<ul>\n<li><strong>Strapi est totalement gratuit</strong></li>\n<li>Vous pouvez l’<strong>héberger sur vos propres serveurs</strong>. Vous êtes donc propriétaire de votre donnée.</li>\n<li>Il est entièrement <strong>personnalisable et extensible</strong>, grâce au système de plugins.</li>\n</ul>\n<h2 id=\"installation-de-l-api\">Installation de l’API</h2>\n<p>Tout d’abord, nous allons commencer par créer une API avec Strapi et ajouter du contenu.</p>\n<h3 id=\"creation-du-projet-strapi\">Création du projet Strapi</h3>\n<h4 id=\"installation-de-strapi\">Installation de Strapi</h4>\n<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>\n<p>Installez Strapi via npm :</p>\n<pre><code class=\"language-bash hljs bash\">npm i strapi@alpha -g</code></pre>\n<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>\n<h4 id=\"creation-d-un-projet-strapi\">Création d’un projet Strapi</h4>\n<p>Créez un dossier nommé <code>gatsby-strapi-tutorial</code> :</p>\n<pre><code class=\"language-bash hljs bash\">mkdir gatsby-strapi-tutorial</code></pre>\n<p>Générez l’API au sein de ce nouveau dossier :</p>\n<pre><code class=\"language-bash hljs bash\"><span class=\"hljs-built_in\">cd</span> gatsby-strapi-tutorial\nstrapi new api</code></pre>\n<h4 id=\"demarrage-du-serveur\">Démarrage du serveur</h4>\n<p>Entrez à l’intérieur du projet généré :</p>\n<pre><code class=\"language-bash hljs bash\"><span class=\"hljs-built_in\">cd</span> api</code></pre>\n<p>Démarrez le serveur Node.js :</p>\n<pre><code class=\"language-bash hljs bash\">strapi start</code></pre>\n<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>\n<h3 id=\"creation-du-premier-utilisateur\">Création du premier utilisateur</h3>\n<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>\n<figure>\n<picture title=\"Écran de création d’un utilisateur\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Écran de création d’un utilisateur</figcaption>\n</figure>\n<h3 id=\"creation-d-un-type-de-contenu\">Création d’un type de contenu</h3>\n<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>\n<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>\n<figure>\n<picture title=\"Écran de définition de relation\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Écran de définition de relation</figcaption>\n</figure>\n<figure>\n<picture title=\"Écran des champs d’un article\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Écran des champs d’un article</figcaption>\n</figure>\n<h3 id=\"ajout-d-articles\">Ajout d’articles</h3>\n<p>Ajoutez quelques articles en base de données. Pour cela, suivez les étapes suivantes :</p>\n<ol>\n<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>\n<li>Cliquer sur <code>Add New Article</code>.</li>\n<li>Renseignez un titre et un contenu, liez l’article à un auteur, puis valider.</li>\n<li>Ajouter deux autres articles.</li>\n</ol>\n<figure>\n<picture title=\"Écran listant des articles\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Écran listant des articles</figcaption>\n</figure>\n<h3 id=\"autorisation-d-acces\">Autorisation d’accès</h3>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"Écran du rôle Guest\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Écran du rôle Guest</figcaption>\n</figure>\n<h2 id=\"developpement-du-site-statique\">Développement du site statique</h2>\n<p>Bien joué, votre API est prête à l’utilisation ! Nous allons maintenant commencer le développement du site statique.</p>\n<h3 id=\"installation-de-gatsby\">Installation de Gatsby</h3>\n<p>Commençez par installer Gatsby :</p>\n<pre><code class=\"language-bash hljs bash\">npm install --global gatsby-cli</code></pre>\n<h3 id=\"creation-d-un-projet-gatsby\">Création d’un projet Gatsby</h3>\n<p>Dans le dossier <code>gatsby-strapi-tutorial</code> que vous avez précédemment créé, générez votre tout nouveau blog :</p>\n<pre><code class=\"language-bash hljs bash\">gatsby new blog</code></pre>\n<h3 id=\"demarrage-en-mode-developpement\">Démarrage en mode développement</h3>\n<p>Entrez dans le dossier du projet :</p>\n<pre><code class=\"language-bash hljs bash\"><span class=\"hljs-built_in\">cd</span> blog</code></pre>\n<p>Démarrez le serveur :</p>\n<pre><code class=\"language-bash hljs bash\">gatsby develop</code></pre>\n<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>\n<h3 id=\"installation-du-plugin-source-strapi\">Installation du plugin source Strapi</h3>\n<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>\n<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>\n<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>\n<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>\n<p>Installons-le :</p>\n<pre><code class=\"language-bash hljs bash\">npm install --save gatsby-source-strapi</code></pre>\n<p>Le plugin a besoin d’être configuré. Remplacez le contenu du fichier <code>gatsby-config.js</code> avec :</p>\n<p><em>Path</em> : <code>gatsby-config.js</code></p>\n<pre><code class=\"language-jsx hljs javascript\"><span class=\"hljs-built_in\">module</span>.exports = {\n  <span class=\"hljs-attr\">siteMetadata</span>: {\n    <span class=\"hljs-attr\">title</span>: <span class=\"hljs-string\">`Gatsby Default Starter`</span>,\n  },\n  <span class=\"hljs-attr\">plugins</span>: [\n    <span class=\"hljs-string\">`gatsby-plugin-react-helmet`</span>,\n    {\n      <span class=\"hljs-attr\">resolve</span>: <span class=\"hljs-string\">`gatsby-source-strapi`</span>,\n      <span class=\"hljs-attr\">options</span>: {\n        <span class=\"hljs-attr\">apiURL</span>: <span class=\"hljs-string\">`http://localhost:1337`</span>,\n        <span class=\"hljs-attr\">contentTypes</span>: [\n          <span class=\"hljs-comment\">// List of the Content Types you want to be able to request from Gatsby.</span>\n          <span class=\"hljs-string\">`article`</span>,\n          <span class=\"hljs-string\">`user`</span>,\n        ],\n      },\n    },\n  ],\n};</code></pre>\n<p>Ensuite, redémarrez le serveur afin que Gatsby prenne en compte ces changements.</p>\n<h3 id=\"liste-d-articles\">Liste d’articles</h3>\n<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>\n<p><em>Path</em> : <code>src/pages/index.js</code></p>\n<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>;\n<span class=\"hljs-keyword\">import</span> Link <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"gatsby-link\"</span>;\n\n<span class=\"hljs-keyword\">const</span> IndexPage = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ data }</span>) =&gt;</span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div</span>&gt;</span>\n    <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>\n    <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>\n    <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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n      {data.allStrapiArticle.edges.map((document) =&gt; (\n        <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>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2</span>&gt;</span>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">h2</span>&gt;</span>\n          <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>\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n      ))}\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span>\n    <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>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span></span>\n);\n\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> IndexPage;\n\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">const</span> pageQuery = graphql<span class=\"hljs-string\">`\n  query IndexQuery {\n    allStrapiArticle {\n      edges {\n        node {\n          id\n          title\n          content\n        }\n      }\n    }\n  }\n`</span>;</code></pre>\n<h4 id=\"que-venons-nous-de-faire\">Que venons-nous de faire ?</h4>\n<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>\n<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>\n<figure>\n<picture title=\"Exemple d’écran d’accueil\">\n<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\">\n<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\">\n<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,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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.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\">\n</picture>\n<figcaption>Exemple d’écran d’accueil</figcaption>\n</figure>\n<h4 id=\"astuce-generez-vos-requetes-graphql-en-quelques-secondes\">Astuce : générez vos requêtes GraphQL en quelques secondes !</h4>\n<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>\n<h3 id=\"vue-article\">Vue article</h3>\n<p>Notre site commence à ressembler à un blog. C’est une bonne nouvelle ! Cependant, une partie importante est encore manquante : la page article.</p>\n<p>Commençons par créer le template contenant la requête GraphQL et définissant le contenu affiché :</p>\n<p><em>Path</em> : <code>src/templates/article.js</code></p>\n<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>;\n<span class=\"hljs-keyword\">import</span> Link <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"gatsby-link\"</span>;\n\n<span class=\"hljs-keyword\">const</span> ArticleTemplate = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ data }</span>) =&gt;</span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div</span>&gt;</span>\n    <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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p</span>&gt;</span>\n      by{\" \"}\n      <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>\n        {data.strapiArticle.author.username}\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">Link</span>&gt;</span>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">p</span>&gt;</span>\n    <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>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span></span>\n);\n\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> ArticleTemplate;\n\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">const</span> query = graphql<span class=\"hljs-string\">`\n  query ArticleTemplate($id: String!) {\n    strapiArticle(id: { eq: $id }) {\n      title\n      content\n      author {\n        id\n        username\n      }\n    }\n  }\n`</span>;</code></pre>\n<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>\n<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>\n<p><em>Path</em> : <code>gatsby-node.js</code></p>\n<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>);\n\n<span class=\"hljs-keyword\">const</span> makeRequest = <span class=\"hljs-function\">(<span class=\"hljs-params\">graphql, request</span>) =&gt;</span>\n  <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> {\n    <span class=\"hljs-comment\">// Query for nodes to use in creating pages.</span>\n    resolve(\n      graphql(request).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">result</span>) =&gt;</span> {\n        <span class=\"hljs-keyword\">if</span> (result.errors) {\n          reject(result.errors);\n        }\n\n        <span class=\"hljs-keyword\">return</span> result;\n      })\n    );\n  });\n\n<span class=\"hljs-comment\">// Implement the Gatsby API “createPages”. This is called once the</span>\n<span class=\"hljs-comment\">// data layer is bootstrapped to let plugins create pages from data.</span>\nexports.createPages = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ boundActionCreators, graphql }</span>) =&gt;</span> {\n  <span class=\"hljs-keyword\">const</span> { createPage } = boundActionCreators;\n\n  <span class=\"hljs-keyword\">const</span> getArticles = makeRequest(\n    graphql,\n    <span class=\"hljs-string\">`\n    {\n      allStrapiArticle {\n        edges {\n          node {\n            id\n          }\n        }\n      }\n    }\n    `</span>\n  ).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">result</span>) =&gt;</span> {\n    <span class=\"hljs-comment\">// Create pages for each article.</span>\n    result.data.allStrapiArticle.edges.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ node }</span>) =&gt;</span> {\n      createPage({\n        <span class=\"hljs-attr\">path</span>: <span class=\"hljs-string\">`/<span class=\"hljs-subst\">${node.id}</span>`</span>,\n        <span class=\"hljs-attr\">component</span>: path.resolve(<span class=\"hljs-string\">`src/templates/article.js`</span>),\n        <span class=\"hljs-attr\">context</span>: {\n          <span class=\"hljs-attr\">id</span>: node.id,\n        },\n      });\n    });\n  });\n\n  <span class=\"hljs-comment\">// Query for articles nodes to use in creating pages.</span>\n  <span class=\"hljs-keyword\">return</span> getArticles;\n};</code></pre>\n<p>Redémarrez le serveur Gatsby.</p>\n<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>\n<figure>\n<picture title=\"Exemple de page\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Exemple de page</figcaption>\n</figure>\n<h3 id=\"vue-auteur\">Vue auteur</h3>\n<p>Les articles sont rédigés par des auteurs. Eux-aussi méritent une page dédiée.</p>\n<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>\n<p><em>Path</em> : <code>src/templates/user.js</code></p>\n<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>;\n<span class=\"hljs-keyword\">import</span> Link <span class=\"hljs-keyword\">from</span> <span class=\"hljs-string\">\"gatsby-link\"</span>;\n\n<span class=\"hljs-keyword\">const</span> UserTemplate = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ data }</span>) =&gt;</span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div</span>&gt;</span>\n    <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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n      {data.strapiUser.articles.map((article) =&gt; (\n        <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>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2</span>&gt;</span>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">h2</span>&gt;</span>\n          <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>\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n      ))}\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span></span>\n);\n\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">default</span> UserTemplate;\n\n<span class=\"hljs-keyword\">export</span> <span class=\"hljs-keyword\">const</span> query = graphql<span class=\"hljs-string\">`\n  query UserTemplate($id: String!) {\n    strapiUser(id: { eq: $id }) {\n      id\n      username\n      articles {\n        id\n        title\n        content\n      }\n    }\n  }\n`</span>;</code></pre>\n<p>Ensuite, nous mettons à jour le fichier <code>gatsby-node.js</code> pour créer les URLs :</p>\n<p><em>Path</em> : <code>gatsby-node.js</code></p>\n<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>);\n\n<span class=\"hljs-keyword\">const</span> makeRequest = <span class=\"hljs-function\">(<span class=\"hljs-params\">graphql, request</span>) =&gt;</span>\n  <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> {\n    <span class=\"hljs-comment\">// Query for article nodes to use in creating pages.</span>\n    resolve(\n      graphql(request).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">result</span>) =&gt;</span> {\n        <span class=\"hljs-keyword\">if</span> (result.errors) {\n          reject(result.errors);\n        }\n\n        <span class=\"hljs-keyword\">return</span> result;\n      })\n    );\n  });\n\n<span class=\"hljs-comment\">// Implement the Gatsby API “createPages”. This is called once the</span>\n<span class=\"hljs-comment\">// data layer is bootstrapped to let plugins create pages from data.</span>\nexports.createPages = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ boundActionCreators, graphql }</span>) =&gt;</span> {\n  <span class=\"hljs-keyword\">const</span> { createPage } = boundActionCreators;\n\n  <span class=\"hljs-keyword\">const</span> getArticles = makeRequest(\n    graphql,\n    <span class=\"hljs-string\">`\n    {\n      allStrapiArticle {\n        edges {\n          node {\n            id\n          }\n        }\n      }\n    }\n    `</span>\n  ).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">result</span>) =&gt;</span> {\n    <span class=\"hljs-comment\">// Create pages for each article.</span>\n    result.data.allStrapiArticle.edges.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ node }</span>) =&gt;</span> {\n      createPage({\n        <span class=\"hljs-attr\">path</span>: <span class=\"hljs-string\">`/<span class=\"hljs-subst\">${node.id}</span>`</span>,\n        <span class=\"hljs-attr\">component</span>: path.resolve(<span class=\"hljs-string\">`src/templates/article.js`</span>),\n        <span class=\"hljs-attr\">context</span>: {\n          <span class=\"hljs-attr\">id</span>: node.id,\n        },\n      });\n    });\n  });\n\n  <span class=\"hljs-keyword\">const</span> getAuthors = makeRequest(\n    graphql,\n    <span class=\"hljs-string\">`\n    {\n      allStrapiUser {\n        edges {\n          node {\n            id\n          }\n        }\n      }\n    }\n    `</span>\n  ).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">result</span>) =&gt;</span> {\n    <span class=\"hljs-comment\">// Create pages for each user.</span>\n    result.data.allStrapiUser.edges.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">{ node }</span>) =&gt;</span> {\n      createPage({\n        <span class=\"hljs-attr\">path</span>: <span class=\"hljs-string\">`/authors/<span class=\"hljs-subst\">${node.id}</span>`</span>,\n        <span class=\"hljs-attr\">component</span>: path.resolve(<span class=\"hljs-string\">`src/templates/user.js`</span>),\n        <span class=\"hljs-attr\">context</span>: {\n          <span class=\"hljs-attr\">id</span>: node.id,\n        },\n      });\n    });\n  });\n\n  <span class=\"hljs-comment\">// Queries for articles and authors nodes to use in creating pages.</span>\n  <span class=\"hljs-keyword\">return</span> <span class=\"hljs-built_in\">Promise</span>.all([getArticles, getAuthors]);\n};</code></pre>\n<p>Dernière étape : redémarrez le serveur et visitez la page auteur depuis la page d’un article.</p>\n<figure>\n<picture title=\"Exemple de page\">\n<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\">\n<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\">\n<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,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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.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\">\n</picture>\n<figcaption>Exemple de page</figcaption>\n</figure>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>Félicitations ! Vous avez créé un blog super rapide et facile à maintenir !</p>\n<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>\n<h3 id=\"que-faire-ensuite\">Que faire ensuite ?</h3>\n<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>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "pierre"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/04/06/comparatif-hugo-jekyll/",
      "url": "https://jamstatic.fr/2018/04/06/comparatif-hugo-jekyll/",
      "title": "Hugo ou Jekyll ? Six critères de comparaison",
      "summary": "Jekyll et Hugo sont parmi les générateurs de site statiques les plus populaires. Quel est celui qu'il vous faut ?",
      "date_published": "2018-04-06T18:10:03+00:00","content_text": "\nChoisir 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.\nQuand 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 ?\nPour 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.\nVersion courte : 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._\nJekyll\nCréé par Tom Preston-Werner, le fondateur de GitHub, Jekyll est à l’origine de la mouvance des sites statiques à laquelle nous assistons actuellement.\nCommencé en 2008, Jekyll est présenté comme un \"générateur de site statique simple, prêt-à-bloguer\".\nC’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 GitHub Pages.\nLa 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 à Liquid, un langage simple utilisé pour définir les gabarits de page.\nInstallation\nInstaller Jekyll n'est pas une mince affaire, surtout sous Windows.\nJekyll est développé en Ruby et demande donc d’avoir une version récente de Ruby installé sur votre machine.\nCe 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 bien documenté.\nContenu\nDans 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.\nLa forme de contenu la plus simple dans Jekyll est stocké à la racine de votre projet sous forme de fichiers au format Markdown ou HTML. 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.\nDes champs Front Matter peuvent être ajoutés à ces fichiers, ils vous permettent de définir les données qui peuvent être utilisées dans vos gabarits.\n---\ntitle: Accueil\ndate: 2017-01-30\ntags: [bonjour, monde]\n---\n## Bonjour monde\nC’est le contenu de ma page !\nJekyll supporte les contenus chronologiques (comme des articles de blog) qui sont stockés dans le dossier _posts et qui respectent la nomenclature yyyy-mm-dd-titre-de-l-article.md.\nJekyll supporte aussi le chargement de données modelées à partir de fichiers YAML, JSON ou CSV situés dans le répertoire _data. Ces données sont accessibles dans vos gabarits à l’aide de {{ site.data }}.\nThèmes et gabarits\nJekyll possède une large communauté et un choix de thèmes gratuits ou payants prêts à l’emploi.\nLes 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.\nLes thèmes pour Jekyll sont développés à l’aide du moteur de templating Liquid 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.\n&lt;div class=“container”&gt;\n{% for post in site.posts %}\n    &lt;div class=\"article\"&gt;\n    &lt;h2&gt;{{ post.title }}&lt;\/h2&gt;\n    &lt;p&gt;{{ post.content }}&lt;\/p&gt;\n    {% for tag in post.tags %}\n        &lt;span&gt;{{ tag }}&lt;\/span&gt;\n    {% endfor %}\n    &lt;\/div&gt;\n{% endfor %}\n&lt;\/div&gt;\nC’est génial pour les débutants et les développeurs qui veulent créer des modèles fonctionnels, propres et simples.\nToutefois, 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.\nPour les développeurs issus du monde des CMS traditionnels comme WordPress, Liquid devrait être facile à prendre en main.\nWorkflow de développement\nDévelopper avec Jekyll, c'est vraiment génial comparé au développement avec des CMS traditionnels propulsés par une base de données.\nJekyll intègre un serveur de développement, qu'on peut lancer avec la commande bundle exec jekyll serve.\nVous 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.\nGestion des assets\nJekyll fournit aussi une gestion intégrée des assets très basique, elle compile les fichiers Sass et CoffeeScript.\nTous les fichiers .scss, .sass ou .coffee qui possèdent des délimiteurs Front Matter seront traités par Jekyll et transformés en fichiers .css et .js.\n---\n---\nalert \"Hello world!\"\nLe 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 Webpack.\nCes 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].\nFonctionnalités utiles\nLe 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:\n\nla gestion des menus,\nla génération de sitemap XML[^core-plugin],\nla génération d’un flux RSS\/Atom[^core-plugin],\nla gestion des scripts Analytics,\nla gestion des commentaires,\nla gestion multilingue\/i18n,\net bien plus…\n\nPour cela il faudra utiliser des plugins Jekyll tiers, qui sont de cinq types :\n\nles générateurs, qui permettent de compléter et de modifier le processus de génération de Jekyll,\nles convertisseurs, qui permettent d’ajouter le support de nouveaux formats de fichiers,\nles commandes, qui permettent d’étendre les options de la ligne de commande de Jekyll,\nles tags, qui permettent d’ajouter de nouvelles balises Liquid,\nles filtres, qui permettent de modifier le rendu des balises Liquid et des variables.\n\nPar exemple, Forestry a dévelopé le plugin jekyll-menus qui permet de gérer les menus dans le CMS Forestry.\nUne autre fonctionnalité bien pratique de Jekyll est l’import de contenus depuis WordPress. Avec 30% de l’Internet enfermé dans WordPress, il est bon de savoir que de migrer vers une stack moderne est aisé.\nPerformance\nForestry a déjà publié un comparatif des performances de Jekyll et Hugo.\nLes résultats des tests montrent que Jekyll est bien plus lent 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.\nJekyll 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…\nCela représente potentiellement plus de 10 heures de perdues lors de la génération chaque année !\nJekyll en résumé\nMaintenant 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.\nPour :\n\nUn moteur de gabarits simple. Les gabarits de page de Jekyll sont très semblables à la syntaxe qu'on trouve dans WordPress ou Craft.\nUn large choix de thèmes. Il existe plein de thèmes prêt à l’emploi pour Jekyll.\nUn large choix de plugins. Il existe des dizaines de plugins pour ajouter les fonctionnalités dont vous avez besoin.\nIntégration dans GitHub Pages. Installer un site avec Jekyll et GitHub Pages est un jeu d’enfant.\n\nContre :\n\nUne génération lente. 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.\nUn manque de fonctionnalités natives. Les fonctionnalités de premier ordre sont mieux supportées et intégrées. Ce point fait défaut à Jekyll.[^plugins]\n\nVous pouvez consulter le guide de Forestry pour développer avec Jekyll pour apprendre à développer un site avec Jekyll et le connecter au CMS Forestry.\nHugo\nHugo est le générateur de site statique créé1 par Steve Francia, un des principaux contributeurs au langage de programmation Go de Google. Hugo est bien entendu développé en Go !\nApparu 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.\nHugo possède un avantage énorme sur tous les autres GSS. Il est rapide.\nIl possède aussi une des communautés les plus (si ce n'est la plus) actives pour un GSS.\nInstallation\nInstaller Hugo est plus simple que d’installer Jekyll, que vous utilisiez Windows ou un système basé sur UNIX.\nVu 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.\nHugo propose une documentation détaillée pour faire cela.\nContenu\nTout comme Jekyll, tous les contenus de votre projet sont stockés dans des fichiers textes.\nDans Hugo, dans les contenus destinés à être générés sont stockés dans le dossier content de votre projet. Vous pouvez utiliser différents formats : Markdown, Mark, et HTML sont supportés par défaut, et il existe des extensions tierces pour supporter Asciidoc and reStructuredText2.\nHugo supporte aussi TOML, YAML, et JSON pour le Front Matter, alors que Jekyll ne supporte que le YAML.\n+++\ntitle = \"Accueil\"\ndate = \"2017-01-30\"\ntags = [\"bonjour\", \"monde\"]\n+++\n## Bonjour Monde\nCeci est un exemple de Front Matter en TOML\nHugo supporte également les données externes, qui peuvent être stockées dans le répertoire \/data 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.\nThèmes &amp; gabarits\nMême si Hugo n'a que 4 ans, de nombreux thèmes sont déjà disponibles pour ce GSS en forte croissance.\nSi vous utilisez la ligne de commande, installer des thèmes depuis le dépôt des thèmes d’Hugo est assez simple.\nHugo utilise le package de template de Go par défaut. Tout comme avec Liquid, il est possible d’ajouter un peu logique dans vos gabarits.\n&lt;div class=“container”&gt;\n{{ range .Site.Pages}\n    &lt;div class=\"article\"&gt;\n    &lt;h2&gt;{{ .Title }}&lt;\/h2&gt;\n    &lt;p&gt;{{ .Content }}&lt;\/p&gt;\n    {{ range .Tags }}\n        &lt;span&gt;{{ . }}&lt;\/span&gt;\n    {{ end }}\n    &lt;\/div&gt;\n{{ end }}\n&lt;\/div&gt;\nUne 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 shortcodes pour ajouter des fonctionnalités supplémentaires.\nMalheureusement 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.\nToutefois, Hugo propose aussi le support des deux moteurs de template Amber et Ace. Ces deux langages pourront sembler plus familiers aux développeurs issus des CMS traditionnels comme WordPress.\nWorkflow de développement\nIl 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.\nDans le dossier de votre projet, lancez la commande hugo serve pour lancer le serveur de développement.\nCela 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.\nGestion des assets\nJusqu'à 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 \/static de votre projet. Maintenant vous n'avez plus besoin d'externaliser votre processus de génération à Webpack ou Gulp pour compiler vos fichiers Sass ou minifier vos CSS et votre JS.\nFonctionnalités utiles\nHugo brille par la multitude de fonctionnalités puissantes qu'il intègre par défaut comparativement à Jekyll et à d’autres GSS.\nAvec 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.\nMais 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.\nPar 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.\nParmi les fonctionnalités bien pratiques d’Hugo on peut citer :\n\nLa gestion des menus,\nLa génération de Sitemap XML,\nLa génération de flux RSS\/Atom,\nL'intégration d’Analytics (via Google Analytics)\nL'intégration de commentaires (via Disqus)\nLa gestion du multilingue\/i18n\nLes formats d’export personnalisés\n\nEnvie de passer à Hugo, mais encore sous Jekyll ?\nHugo peut importer votre site Jekyll en ligne de commande !\nPerformance\nHugo est extrêmement rapide. Forestry a publié un article sur la performance de Hugo et de Jekyll et a comparé les deux. Hugo est sorti vainqueur haut la main.\nRendez-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.\nLors d’un test, @darinpope un utilisateur d’Hugo a réussi à générer 600 000 pages en moins de 5 minutes !.\nHugo en résumé\nMaintenant 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.\nPour :\n\nExtrêmement rapide. Des temps de génération de l’ordre de la seconde.\nExtrêmement versatile. Plein de fonctionnalités par défaut pour des sites web d’entreprises.\nParé pour l’entreprise Avec le support des exports multiples et des sites multilingues, vous êtes opérationnel !\nUne communauté florissante. Il est facile d’avoir de l’aide. Posez une question sur le forum et vous aurez une réponse.\n\nContre :\n\nPas d’extensions. Hugo ne prend pas les plugins en charge, il n'est donc pas possible d’ajouter des fonctionnalités personnalisées.\nUne syntaxe de gabarit compliquée. Bien que le moteur de gabarits d’Hugo soit versatile, il est assez peu intuitif et compliqué pour les débutants.\n\nReportez-vous au guide de Forestry pour développer avec Hugo pour apprendre comment développer un site avec Hugo et le connecter au CMS Forestry.\nEn résumé\nNous 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.\nCes deux générateurs sont les leaders dans leur domaine, et il y a plein d’exemples de gros projets qui les utilisent comme healthcare.gov, développé avec Jekyll, et le nouveau site de Smashing Magazine développé avec Hugo.\nMaintenant c'est l’heure de faire votre choix ! Voici un petit récapitulatif pour vous aider :\n\nJekyll 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.\nHugo 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.\n\n\n\n\n\nHugo a été depuis principalement développé par Bjørn Erik Pedersen.&#160;&#8617;\n\n\nNdT: à 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;&#8617;\n\n\n",
      "content_html": "<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"jekyll\">Jekyll</h2>\n<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>\n<p>Commencé en 2008, Jekyll est présenté comme un \"générateur de site statique simple, prêt-à-bloguer\".</p>\n<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>\n<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>\n<h3 id=\"installation\">Installation</h3>\n<p>Installer Jekyll n'est pas une mince affaire, surtout sous Windows.</p>\n<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>\n<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>\n<h3 id=\"contenu\">Contenu</h3>\n<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>\n<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>\n<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>\n<pre><code class=\"language-md hljs markdown\">---\ntitle: Accueil\ndate: 2017-01-30\n<span class=\"hljs-section\">tags: [bonjour, monde]\n---</span>\n<span class=\"hljs-section\">## Bonjour monde</span>\nC’est le contenu de ma page !</code></pre>\n<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>\n<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>\n<h3 id=\"themes-et-gabarits\">Thèmes et gabarits</h3>\n<p>Jekyll possède une large communauté et un choix de thèmes gratuits ou payants prêts à l’emploi.</p>\n<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>\n<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>\n<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>\n{% for post in site.posts %}\n    <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>\n    <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>\n    <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>\n    {% for tag in post.tags %}\n        <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>\n    {% endfor %}\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n{% endfor %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span></code></pre>\n<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>\n<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>\n<p>Pour les développeurs issus du monde des CMS traditionnels comme WordPress, Liquid devrait être facile à prendre en main.</p>\n<h3 id=\"workflow-de-developpement\">Workflow de développement</h3>\n<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>\n<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>\n<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>\n<h4 id=\"gestion-des-assets\">Gestion des assets</h4>\n<p>Jekyll fournit aussi une gestion intégrée des assets très basique, elle compile les fichiers Sass et CoffeeScript.</p>\n<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>\n<pre><code class=\"language-js hljs javascript\">---\n---\nalert <span class=\"hljs-string\">\"Hello world!\"</span></code></pre>\n<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>\n<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>\n<h3 id=\"fonctionnalites-utiles\">Fonctionnalités utiles</h3>\n<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>\n<ul>\n<li>la gestion des menus,</li>\n<li>la génération de sitemap XML[^core-plugin],</li>\n<li>la génération d’un flux RSS/Atom[^core-plugin],</li>\n<li>la gestion des scripts Analytics,</li>\n<li>la gestion des commentaires,</li>\n<li>la gestion multilingue/i18n,</li>\n<li>et bien plus…</li>\n</ul>\n<p>Pour cela il faudra utiliser des plugins Jekyll tiers, qui sont de cinq types :</p>\n<ul>\n<li>les <strong>générateurs</strong>, qui permettent de compléter et de modifier le processus de génération de Jekyll,</li>\n<li>les <strong>convertisseurs</strong>, qui permettent d’ajouter le support de nouveaux formats de fichiers,</li>\n<li>les <strong>commandes</strong>, qui permettent d’étendre les options de la ligne de commande de Jekyll,</li>\n<li>les <strong>tags</strong>, qui permettent d’ajouter de nouvelles balises Liquid,</li>\n<li>les <strong>filtres</strong>, qui permettent de modifier le rendu des balises Liquid et des variables.</li>\n</ul>\n<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>\n<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>\n<h3 id=\"performance\">Performance</h3>\n<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>\n<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>\n<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>\n<p>Cela représente potentiellement plus de 10 heures de perdues lors de la génération chaque année !</p>\n<h3 id=\"jekyll-en-resume\">Jekyll en résumé</h3>\n<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>\n<p><strong>Pour :</strong></p>\n<ul>\n<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>\n<li>Un <strong>large choix de thèmes.</strong> Il existe plein de thèmes prêt à l’emploi pour Jekyll.</li>\n<li>Un <strong>large choix de plugins.</strong> Il existe des dizaines de plugins pour ajouter les fonctionnalités dont vous avez besoin.</li>\n<li><strong>Intégration dans GitHub Pages.</strong> Installer un site avec Jekyll et GitHub Pages est un jeu d’enfant.</li>\n</ul>\n<p><strong>Contre :</strong></p>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<h2 id=\"hugo\">Hugo</h2>\n<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>\n<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>\n<p>Hugo possède un avantage énorme sur tous les autres GSS. Il est <strong>rapide</strong>.</p>\n<p>Il possède aussi une des communautés les plus (si ce n'est <em>la</em> plus) actives pour un GSS.</p>\n<h3 id=\"installation-1\">Installation</h3>\n<p>Installer Hugo est plus simple que d’installer Jekyll, que vous utilisiez Windows ou un système basé sur UNIX.</p>\n<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>\n<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>\n<h3 id=\"contenu-1\">Contenu</h3>\n<p>Tout comme Jekyll, tous les contenus de votre projet sont stockés dans des fichiers textes.</p>\n<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>\n<p>Hugo supporte aussi <strong>TOML, YAML, et JSON</strong> pour le Front Matter, alors que Jekyll ne supporte que le YAML.</p>\n<pre><code class=\"language-toml hljs ini\">+++\n<span class=\"hljs-attr\">title</span> = <span class=\"hljs-string\">\"Accueil\"</span>\n<span class=\"hljs-attr\">date</span> = <span class=\"hljs-string\">\"2017-01-30\"</span>\n<span class=\"hljs-attr\">tags</span> = [<span class=\"hljs-string\">\"bonjour\"</span>, <span class=\"hljs-string\">\"monde\"</span>]\n+++\n<span class=\"hljs-comment\">## Bonjour Monde</span>\nCeci est un exemple de Front Matter en TOML</code></pre>\n<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>\n<h3 id=\"themes-gabarits\">Thèmes &amp; gabarits</h3>\n<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>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">&lt;div class=“container”&gt;\n{{ <span class=\"hljs-keyword\">range</span> .Site.Pages}\n    &lt;div class=<span class=\"hljs-string\">\"article\"</span>&gt;\n    &lt;h2&gt;{{ .Title }}&lt;/h2&gt;\n    &lt;p&gt;{{ .Content }}&lt;/p&gt;\n    {{ <span class=\"hljs-keyword\">range</span> .Tags }}\n        &lt;span&gt;{{ . }}&lt;/span&gt;\n    {{ end }}\n    &lt;/div&gt;\n{{ end }}\n&lt;/div&gt;</code></pre>\n<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>\n<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>\n<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>\n<h3 id=\"workflow-de-developpement-1\">Workflow de développement</h3>\n<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>\n<p>Dans le dossier de votre projet, lancez la commande <code>hugo serve</code> pour lancer le serveur de développement.</p>\n<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>\n<h4 id=\"gestion-des-assets-1\">Gestion des assets</h4>\n<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>\n<h3 id=\"fonctionnalites-utiles-1\">Fonctionnalités utiles</h3>\n<p>Hugo brille par la multitude de fonctionnalités puissantes qu'il intègre par défaut comparativement à Jekyll et à d’autres GSS.</p>\n<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>\n<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>\n<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>\n<p>Parmi les fonctionnalités bien pratiques d’Hugo on peut citer :</p>\n<ul>\n<li>La gestion des menus,</li>\n<li>La génération de Sitemap XML,</li>\n<li>La génération de flux RSS/Atom,</li>\n<li>L'intégration d’Analytics (via Google Analytics)</li>\n<li>L'intégration de commentaires (via Disqus)</li>\n<li>La gestion du multilingue/i18n</li>\n<li>Les formats d’export personnalisés</li>\n</ul>\n<aside class=\"note note-tip\"><p>Envie de passer à Hugo, mais encore sous Jekyll ?\n<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>\n<h3 id=\"performance-1\">Performance</h3>\n<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>\n<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>\n<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>\n<h3 id=\"hugo-en-resume\">Hugo en résumé</h3>\n<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>\n<p><strong>Pour :</strong></p>\n<ul>\n<li><strong>Extrêmement rapide.</strong> Des temps de génération de l’ordre de la seconde.</li>\n<li><strong>Extrêmement versatile.</strong> Plein de fonctionnalités par défaut pour des sites web d’entreprises.</li>\n<li><strong>Paré pour l’entreprise</strong> Avec le support des exports multiples et des sites multilingues, vous êtes opérationnel !</li>\n<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>\n</ul>\n<p><strong>Contre :</strong></p>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<h2 id=\"en-resume\">En résumé</h2>\n<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>\n<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>\n<p>Maintenant c'est l’heure de faire votre choix ! Voici un petit récapitulatif pour vous aider :</p>\n<ul>\n<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>\n<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>\n</ul>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:hugo\">\n<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>\n</li>\n<li id=\"fn:extensions\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/04/04/contenus-relatifs-dans-hugo/",
      "url": "https://jamstatic.fr/2018/04/04/contenus-relatifs-dans-hugo/",
      "title": "Entretenir de bonnes relations avec Hugo",
      "summary": "Définissez des relations entre vos différents types de contenus dans Hugo de façon performante.",
      "date_published": "2018-04-04T20:25:16+00:00","content_text": "Même s'il est le plus rapide des générateurs de site statiques,\nHugo continue de s'améliorer et de proposer de nouvelles fonctionnalités pour\nnous simplifier la vie. Régis Philibert a testé\npour vous la gestion des contenus relatifs apparus dans la version 0.27.\nJe me suis enfin décidé à améliorer la façon dont je gère les relations entre\nles contenus dans mes projets en utilisant la fonctionnalité dédiée aux contenus\nrelatifs proposée par Hugo.[^1]\nEn faisant cela, j'ai diminué le temps de génération du site d’environ 70%\n⏱️ 👀!\nDans cet article, nous allons voir comme l’implémentation de relations entre vos\ncontenus est facile à ajouter sur un projet existant et comment cela va changer\nà jamais la façon dont nous définissons les relations dans Hugo !\n\nLe Projet\nJ'ai créé et commencé à maintenir un site web open source en français sur la\nsaga des Rougon-Macquart\nd’Émile Zola bien avant que je\ncommence à coder.\nCopier-coller la biographie de tous mes personnages dans WordPress m'a pris pas\nmal de temps, mais je me retrouve maintenant avec le projet idéal pour tester de\nnouveaux outils : l’API Rest de WordPress, AngularJS et plus récemment Hugo !\nAvec un millier d’entrées qui partagent des relations saines, c'est le projet\nparfait pour tester une nouvelle manière de gérer nos relations.\n\nChacun 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.\nDans chacun des 20 romans apparaissent de nombreux personnages. Sur la page de chaque roman figure tous les personnages qui y apparaissent.\n\nStatut des relations avant Related Content: c'est compliqué\nIl n'y avait pas de méthode claire pour connecter des pages entre elles et créer\ndes relations durables et efficaces. La première chose qui venait souvent à\nl’esprit était d’utiliser les taxonomies, mais ça ne marchait pas lorsqu'il\ns'agissait de connecter des pages entre elles.\nUne fois les taxonomies écartées, si vous deviez gérer des relations\nde plusieurs à un,\nvous avez peut-être utilisé les sections avec la plus grande prudence.\nMais quand il s'agit d’implémenter la plus commune des\nrelations de plusieurs à plusieurs,\nje trouve que la solution la plus sensée est de créer une relation via une\nentrée Front Matter dans les pages concernées. Pour les Rougon-Macquart de Zola,\nc'était indéniablement le cas.\nL'implementation dans le Front Matter\nDans ce projet, les romans peuvent compter jusqu'à 90 personnages. Ce qui\nsignifie qui si nous devions lister tous les personnages présents dans un roman,\nnous nous retrouverions avec un tableau de 90 entrées en entête de notre fichier\nMarkdown. C’est vraiment loin d’être idéal.\nDe plus nous n'avons pas vraiment besoin de référencer la connexion de notre\nrelation à la fois dans les pages de romans et dans les pages des personnages.\nLes personnages ne sont présents que dans 4 à 5 romans tout au plus, il vaut\ndonc mieux déclarer les quelques romans dans lesquels ils apparaissent\nplutôt que de lister les nombreux personnages pour chaque roman.\nPar exemple pour le personnage d’Eugène Rougon, qui figure dans 4 romans, cela\ndonne :\ntitle: Rougon (Eugène)\nnovel:\n  - argent\n  - curee\n  - fortune\n  - excellence\nMaintenant dans le Front Matter du roman, nous avons juste à ajouter une clef\nd’identifiant. Pour le roman « Son Excellence Eugène Rougon » dans lequel\napparaît ce bon vieil Eugène nous ajoutons :\ntitle: Son excellence Eugène Rougon\nid: excellence\nNous pourrions choisir un identifiant existant comme le nom de\nfichier, mais je préfère un identifiant unique, facile à lire et à écrire.\nLes relations dans nos gabarits de page\nSur\nla page d’Eugène\nnous voulons afficher les romans dans lesquels il apparaît. Nous pouvons\nutiliser intersect pour construire notre liste :\n{{ $characters := where .Site.Pages.ByTitle \".Params.novel\" \"intersect\" (slice .Params.id) }}\nPour afficher la liste des personnages du roman sur la page\nSon Excellence Eugène Rougon,\nnous utilisons l’opérateur in avec where:\n{{ $novels := where .Site.Pages.ByTitle \".Params.id\" \"in\" .Params.novel }}\nEt voilà, nous avons réussi à implémenter une relation de type plusieurs à\nplusieurs comme si nous étions en 2016 !\nCar cela a le mérite de fonctionner mais…\n\n\ninteresect ? where \"in\" ? N’en faisons-nous pas un peu trop ?\n\n\n🐌 Le temps de génération est 7 fois supérieur à la moyenne : ~7 secondes pour 1300 pages.\n\n\n💩 C’est moche.\n\n\nOK… mais que pouvons-nous y faire ? 🤷‍♂️\nRien… enfin jusqu'à la version 0.27 d’Hugo.\nLa fonctionnalité Related Content d’Hugo\nLes contenus relatifs natifs\nont fait leur apparition dans Hugo 0.27 en novembre 2017.\nIls ont été conçus pour aider à ajouter facilement une section « Vous aimerez\naussi : » dans les thèmes et les projets tout en gardant un maximum de\ncontrôle sur l’algorithme de pondération. Vous pouvez définir plusieurs facteurs\nou index en leur affectant leur propre niveau d’importance. Les tags, le mois de\npublication, les auteurs, tout ce qui peut vous aider à construire une liste de\ncontenus relatifs pertinente.\nC’est de loin de meilleur outil pour récupérer des pages relatives à une autre à\nl’aide de votre propre formule et si vous ne l’utilisez pas déjà pour générer\nvotre widget \"Articles\/Produits liés\", vous devriez aller de ce pas consulter\nla documentation pour commencer\nà jouer avec. C’est top !\nNéanmoins dans notre cas, nous n'avons pas besoin d’un module \"romans relatifs\",\nnous avons juste besoin d’établir une relation solide et consistante qui\nn’impacte pas le temps de génération du site. Et il se trouve que c'est\njustement ce que propose la fonctionnalité Related Content !\nNous n'avons même pas besoin de recourir à l’ingénieux facteur de poids d’index\npuisque novel est notre seul et unique index.\nÉtablir des relations avec Related Content\nDéclarer notre index\nEn premier nous devons déclarer la liste de nos index dans notre fichier de\nconfiguration config.yaml. Vu qu'ici nous n' avons que novel comme index…\nrelated:\n indices:\n   - name: novel # Le nom de l’index, tel qu'il est défini la clef `.Param` du Front Matter.\n     weight: 1 # Nous n'avons pas vraiment besoin, mais si nous l’omettons cela désactivera notre index.\n     includeNewer: true # Ici notre relation est sans fin ! Cette option empêche Hugo d’ignorer les nouveaux articles.\nBien se connecter\nL'entête Front Matter de notre personnage est très bien comme elle est. Elle\nliste déjà les romans à l’aide d’une clef qui correspond au nom de notre index\nnovel.\nPar contre, nos romans utilisent id pour s'identifier, il faut changer ça car\nils doivent également utiliser le même nom d’index. Donc l’entête Front Matter\nde notre roman devient :\ntitle: Son Excellence Eugène Rougon\nnovel: excellence # 'id’ précédemment\nBien, nos romans et nos personnages partagent maintenant un .Page.Param commun\nqui utilise le nom de notre index nouvellement déclaré : novel.\nRelated Content dans les gabarits de page\nDans nos gabarits de page, Related offre différentes fonctions pour récupérer\nles pages relatives. Nous allons en voir deux succinctement, mais allez lire\nla documentation\nsi vous souhaitez en apprendre davantage.\n.Related permet de récupérer toutes les pages relatives d’une page donnée\nen fonction des index et du poids déclarés dans le fichier de configuration.\nElle prend un seul paramètre en argument : la page donnée.\n.RelatedIndices permet de récupérer toutes les pages qui comportent un ou\nplusieurs index donnés. Le premier paramètre est la page donnée, les autres\nparamètres sont les index utilisés.\nDans nos gabarits de page de détail, nous allons utiliser la fonction\n.RelatedIndices pour récuperer les romans ou les personnages reliés. Ceci afin\nde limiter les pages reliées à notre index novel et empêcher que de futures\nindex comme des tags ou un auteur viennent interférer dans notre relation\nexistante.\nDans le gabarit de page de détail d’un roman comme \"Son Excellence Eugène\nRougon\", nous pouvons lister tous ses « characters », en anglais dans le texte,\nde la façon suivante :\n{{ $characters := where (.Site.RegularPages.RelatedIndices . \"novel\" ) \"Type\" \"personnage\" }}\nLe premier paramètre c'est le contexte de notre page, le second c'est notre\nfameux index.\nEt pour la page de présentation d’un personnage comme Eugène, pour récupérer\ntoutes ses « novels » :\n{{ $novels := where (.Site.RegularPages.RelatedIndices . \"novel\" ) \"Type\" \"roman\" }}\nEt voilà ! Nous utilisons maintenant la fonction Related Content d’Hugo pour\ngérer nos relations de type plusieurs à plusieurs !\nEt qu'avons-nous gagné outre un code plus propre ?\n🚀 6 secondes de moins ! …sur les ~7s auparavant…\nLe temps de génération n'excède maintenant pas les 1.5s. Dans le mille Émile !\nSi vous êtes curieux, vous pouvez cloner le\nrepo et vous en donner à cœur joie\navec la commande hugo --templateMetrics. Vous pouvez même passer sur la\nbranche\noldRelationship\net comparer avec l’implémentation précédente des relations.\nConclusion\nEn ayant simplement recours à la fonction Related Content native dans Hugo\nplutôt qu'à un horrible patch fait maison, nous avons réduit le temps de\ngénération de plus de 70% et tout ça avec un minimum de changement dans\nnotre code.\nIl y a un énorme avantage à tirer profit des super pouvoirs des nouvelles\nfonctionnalités natives d’Hugo et ce modeste article a tenté de vous montrer à\nquel point il est simple de commencer à utiliser et à implémenter l’une d’entre\nelles dans vos projets existants.",
      "content_html": "<aside class=\"note note-intro\"><p>Même s'il est le plus rapide des générateurs de site statiques,\nHugo continue de s'améliorer et de proposer de nouvelles fonctionnalités pour\nnous simplifier la vie. <a href=\"https://regisphilibert.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Régis Philibert</a> a testé\npour vous la gestion des contenus relatifs apparus dans la version <code>0.27</code>.</p></aside>\n<p>Je me suis enfin décidé à améliorer la façon dont je gère les relations entre\nles contenus dans mes projets en utilisant la fonctionnalité dédiée aux contenus\nrelatifs proposée par Hugo.[^1]</p>\n<p><strong>En faisant cela, j'ai diminué le temps de génération du site d’environ 70%</strong>\n⏱️ 👀!</p>\n<p>Dans cet article, nous allons voir comme l’implémentation de relations entre vos\ncontenus est facile à ajouter sur un projet existant et comment cela va changer\nà jamais la façon dont nous définissons les relations dans Hugo !</p>\n<!--more-->\n<h2 id=\"le-projet\">Le Projet</h2>\n<p>J'ai créé et commencé à maintenir un site web open source en français sur la\nsaga des <a href=\"https://rougon-macquart.com\" target=\"_blank\" rel=\"noopener noreferrer\">Rougon-Macquart</a>\nd’<a href=\"https://fr.wikipedia.org/wiki/%C3%89mile_Zola\" target=\"_blank\" rel=\"noopener noreferrer\">Émile Zola</a> bien avant que je\ncommence à coder.</p>\n<p>Copier-coller la biographie de tous mes personnages dans WordPress m'a pris pas\nmal de temps, mais je me retrouve maintenant avec le projet idéal pour tester de\nnouveaux outils : l’API Rest de WordPress, AngularJS et plus récemment Hugo !</p>\n<p>Avec un millier d’entrées qui partagent des relations saines, c'est le projet\nparfait pour tester une nouvelle manière de gérer nos relations.</p>\n<ul>\n<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>\n<li>Dans chacun des 20 romans apparaissent de nombreux personnages. Sur la page de chaque roman figure tous les personnages qui y apparaissent.</li>\n</ul>\n<h2 id=\"statut-des-relations-avant-related-content-c-est-complique\">Statut des relations avant <em>Related Content</em>: c'est compliqué</h2>\n<p>Il n'y avait pas de méthode claire pour connecter des pages entre elles et créer\ndes relations durables et efficaces. La première chose qui venait souvent à\nl’esprit était d’utiliser les taxonomies, mais ça ne marchait pas lorsqu'il\ns'agissait de connecter des pages entre elles.</p>\n<p>Une fois les taxonomies écartées, si vous deviez gérer des relations\n<a href=\"https://fr.wikipedia.org/wiki/Relation_de_plusieurs_%C3%A0_un\" target=\"_blank\" rel=\"noopener noreferrer\">de plusieurs à un</a>,\nvous avez peut-être utilisé les <code>sections</code> avec la plus grande prudence.</p>\n<p>Mais quand il s'agit d’implémenter la plus commune des\n<a href=\"&lt;https://en.wikipedia.org/wiki/Many-to-many_(data_model)&gt;\">relations de plusieurs à plusieurs</a>,\nje trouve que la solution la plus sensée est de créer une relation via une\nentrée Front Matter dans les pages concernées. Pour les Rougon-Macquart de Zola,\nc'était indéniablement le cas.</p>\n<h3 id=\"l-implementation-dans-le-front-matter\">L'implementation dans le Front Matter</h3>\n<p>Dans ce projet, les romans peuvent compter jusqu'à 90 personnages. Ce qui\nsignifie qui si nous devions lister tous les personnages présents dans un roman,\nnous nous retrouverions avec un tableau de 90 entrées en entête de notre fichier\nMarkdown. C’est vraiment loin d’être idéal.</p>\n<p>De plus nous n'avons pas vraiment besoin de référencer la connexion de notre\nrelation à la fois dans les pages de romans <em>et</em> dans les pages des personnages.\nLes personnages ne sont présents que dans 4 à 5 romans tout au plus, il vaut\ndonc mieux déclarer les <strong>quelques</strong> romans dans lesquels ils apparaissent\nplutôt que de lister les <strong>nombreux</strong> personnages pour chaque roman.</p>\n<p>Par exemple pour le personnage d’<em>Eugène Rougon</em>, qui figure dans 4 romans, cela\ndonne :</p>\n<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>\n<span class=\"hljs-attr\">novel:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">argent</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">curee</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">fortune</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">excellence</span></code></pre>\n<p>Maintenant dans le Front Matter du roman, nous avons juste à ajouter une clef\nd’identifiant. Pour le roman « Son Excellence Eugène Rougon » dans lequel\napparaît ce bon vieil Eugène nous ajoutons :</p>\n<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>\n<span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">excellence</span></code></pre>\n<aside class=\"note\"><p>Nous pourrions choisir un identifiant existant comme le nom de\nfichier, mais je préfère un identifiant unique, facile à lire et à écrire.</p></aside>\n<h4 id=\"les-relations-dans-nos-gabarits-de-page\">Les relations dans nos gabarits de page</h4>\n<p>Sur\n<a href=\"https://rougon-macquart.com/personnage/2010-03-15-rougon-eugene/\" target=\"_blank\" rel=\"noopener noreferrer\">la page d’Eugène</a>\nnous voulons afficher les romans dans lesquels il apparaît. Nous pouvons\nutiliser <code>intersect</code> pour construire notre liste :</p>\n<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>\n<p>Pour afficher la liste des personnages du roman sur la page\n<a href=\"https://rougon-macquart.com/roman/1876-son-excellence-eugene-rougon/\" target=\"_blank\" rel=\"noopener noreferrer\">Son Excellence Eugène Rougon</a>,\nnous utilisons l’opérateur <code>in</code> avec <code>where</code>:</p>\n<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>\n<p>Et voilà, nous avons réussi à implémenter une relation de type plusieurs à\nplusieurs comme si nous étions en 2016 !</p>\n<p>Car cela a le mérite de fonctionner mais…</p>\n<ol>\n<li>\n<p><code>interesect</code> ? <code>where \"in\"</code> ? N’en faisons-nous pas un peu trop ?</p>\n</li>\n<li>\n<p>🐌 Le temps de génération est <strong>7 fois</strong> supérieur à la moyenne : ~7 secondes pour 1300 pages.</p>\n</li>\n<li>\n<p>💩 C’est moche.</p>\n</li>\n</ol>\n<p>OK… mais que pouvons-nous y faire ? 🤷‍♂️</p>\n<p>Rien… enfin jusqu'à la version 0.27 d’Hugo.</p>\n<h2 id=\"la-fonctionnalite-related-content-d-hugo\">La fonctionnalité <em>Related Content</em> d’Hugo</h2>\n<p><a href=\"https://gohugo.io/content-management/related/\" target=\"_blank\" rel=\"noopener noreferrer\">Les contenus relatifs natifs</a>\nont fait leur apparition dans Hugo 0.27 en novembre 2017.</p>\n<p>Ils ont été conçus pour aider à ajouter facilement une section <strong>« Vous aimerez\naussi : »</strong> dans les thèmes et les projets tout en gardant un maximum de\ncontrôle sur l’algorithme de pondération. Vous pouvez définir plusieurs facteurs\nou index en leur affectant leur propre niveau d’importance. Les tags, le mois de\npublication, les auteurs, tout ce qui peut vous aider à construire une liste de\ncontenus relatifs pertinente.</p>\n<p>C’est de loin de meilleur outil pour récupérer des pages relatives à une autre à\nl’aide de votre <em>propre</em> formule et si vous ne l’utilisez pas déjà pour générer\nvotre widget \"Articles/Produits liés\", vous devriez aller de ce pas consulter\n<a href=\"https://gohugo.io/content-management/related/\" target=\"_blank\" rel=\"noopener noreferrer\">la documentation</a> pour commencer\nà jouer avec. C’est top !</p>\n<p>Néanmoins dans notre cas, nous n'avons pas besoin d’un module \"romans relatifs\",\nnous avons juste besoin d’établir une relation solide et consistante qui\nn’impacte pas le temps de génération du site. Et il se trouve que c'est\njustement ce que propose la fonctionnalité <em>Related Content</em> !</p>\n<p>Nous n'avons même pas besoin de recourir à l’ingénieux facteur de poids d’index\npuisque <code>novel</code> est notre seul et unique index.</p>\n<h3 id=\"etablir-des-relations-avec-related-content\">Établir des relations avec <em>Related Content</em></h3>\n<h4 id=\"declarer-notre-index\">Déclarer notre index</h4>\n<p>En premier nous devons déclarer la liste de nos index dans notre fichier de\nconfiguration <code>config.yaml</code>. Vu qu'ici nous n' avons que <code>novel</code> comme index…</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">related:</span>\n <span class=\"hljs-attr\">indices:</span>\n   <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>\n     <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>\n     <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>\n<h4 id=\"bien-se-connecter\">Bien se connecter</h4>\n<p>L'entête Front Matter de notre personnage est très bien comme elle est. Elle\nliste déjà les romans à l’aide d’une clef qui correspond au nom de notre index\n<code>novel</code>.</p>\n<p>Par contre, nos romans utilisent <code>id</code> pour s'identifier, il faut changer ça car\nils doivent également utiliser le même nom d’index. Donc l’entête Front Matter\nde notre roman devient :</p>\n<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>\n<span class=\"hljs-attr\">novel:</span> <span class=\"hljs-string\">excellence</span> <span class=\"hljs-comment\"># 'id’ précédemment</span></code></pre>\n<p>Bien, nos romans et nos personnages partagent maintenant un <code>.Page.Param</code> commun\nqui utilise le nom de notre index nouvellement déclaré : <code>novel</code>.</p>\n<h4 id=\"related-content-dans-les-gabarits-de-page\"><em>Related Content</em> dans les gabarits de page</h4>\n<p>Dans nos gabarits de page, <em>Related</em> offre différentes fonctions pour récupérer\nles pages relatives. Nous allons en voir deux succinctement, mais allez lire\n<a href=\"https://gohugo.io/content-management/related/#list-related-content\" target=\"_blank\" rel=\"noopener noreferrer\">la documentation</a>\nsi vous souhaitez en apprendre davantage.</p>\n<p><strong>.Related</strong> <em>permet de récupérer toutes les pages relatives d’une page donnée\nen fonction des index et du poids déclarés dans le fichier de configuration.\nElle prend un seul paramètre en argument : la page donnée.</em></p>\n<p><strong>.RelatedIndices</strong> <em>permet de récupérer toutes les pages qui comportent un ou\nplusieurs index donnés. Le premier paramètre est la page donnée, les autres\nparamètres sont les index utilisés.</em></p>\n<p>Dans nos gabarits de page de détail, nous allons utiliser la fonction\n<code>.RelatedIndices</code> pour récuperer les romans ou les personnages reliés. Ceci afin\nde limiter les pages reliées à notre index <code>novel</code> et empêcher que de futures\nindex comme des tags ou un auteur viennent interférer dans notre relation\nexistante.</p>\n<p>Dans le gabarit de page de détail d’un roman comme \"Son Excellence Eugène\nRougon\", nous pouvons lister tous ses « characters », en anglais dans le texte,\nde la façon suivante :</p>\n<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>\n<p><em>Le premier paramètre c'est le contexte de notre page, le second c'est notre\nfameux index</em>.</p>\n<p>Et pour la page de présentation d’un personnage comme Eugène, pour récupérer\ntoutes ses « novels » :</p>\n<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>\n<p>Et voilà ! Nous utilisons maintenant la fonction <em>Related Content</em> d’Hugo pour\ngérer nos relations de type plusieurs à plusieurs !</p>\n<p>Et qu'avons-nous gagné outre un code plus propre ?</p>\n<p>🚀 <strong>6 secondes de moins !</strong> …sur les ~7s auparavant…</p>\n<p>Le temps de génération n'excède maintenant pas les 1.5s. Dans le mille Émile !</p>\n<aside class=\"note note-info\"><p>Si vous êtes curieux, vous pouvez cloner le\n<a href=\"https://github.com/regisphilibert/rougon\" target=\"_blank\" rel=\"noopener noreferrer\">repo</a> et vous en donner à cœur joie\navec la commande <code>hugo --templateMetrics</code>. Vous pouvez même passer sur la\nbranche\n<a href=\"https://github.com/regisphilibert/rougon/tree/oldRelationships\" target=\"_blank\" rel=\"noopener noreferrer\"><code>oldRelationship</code></a>\net comparer avec l’implémentation précédente des relations.</p></aside>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>En ayant simplement recours à la fonction <em>Related Content</em> native dans Hugo\nplutôt qu'à un horrible patch fait maison, <strong>nous avons réduit le temps de\ngénération de plus de 70%</strong> et tout ça avec un <strong>minimum de changement dans\nnotre code</strong>.</p>\n<p>Il y a un énorme avantage à tirer profit des super pouvoirs des nouvelles\nfonctionnalités natives d’Hugo et ce modeste article a tenté de vous montrer à\nquel point il est simple de commencer à utiliser et à implémenter l’une d’entre\nelles dans vos projets existants.</p>",
      "authors": [
        {
          "name": "regis"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/03/19/demarrer-avec-algolia/",
      "url": "https://jamstatic.fr/2018/03/19/demarrer-avec-algolia/",
      "title": "Bien démarrer avec Algolia",
      "summary": "Jess West vous dit comment indexer et intégrer une recherche performante pour son site.",
      "date_published": "2018-03-19T20:13:46+00:00","content_text": "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.\nSalut 👋 ! Ç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…\nÇ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, où est-ce qu'est censé aller ce module ? Vraiment, c'est pas marrant à faire.\nMais 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.\nC’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.\nObtenir vos clefs d’API\n\n\n\n\n\n\nLes différentes clés d’API d’Algolia\n\nCommencez par créer un compte chez Algolia. Et récupérez ensuite vos identifiants dans votre dashboard. Vous aurez besoin de copier App Id, Search Only API Key et Admin API Key.\nUne fois que c'est fait, ajoutez-les dans ce que vous utilisez pour stocker vos variables d’environnement (un fichier .env 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 !\nConnecter votre source de données\nSi 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.\nconst data_url = \"https:\/\/raw.githubusercontent.com\/algolia\/datasets\/master\/movies\/actors.json\";\n\nfunction indexData(data_url) {\n  return axios\n    .get(data_url, {})\n    .then(function (response) {\n      console.log(response.data[0]);\n      return;\n    })\n    .catch(function (error) {\n      console.warn(error);\n    });\n}\nPour 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.\nPréparer les données pour Algolia\nMaintenant 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 axios.get 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.\nPremière étape : 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 axios.get.\nfunction indexData(data_url) {\n  return axios\n    .get(data_url, {})\n    .then((response) =&gt; {\n      return dataToAlgoliaObject(response.data);\n    })\n    .then(function (response) {\n      return;\n    })\n    .catch(function (error) {\n      console.warn(error);\n    });\n}\nMaintenant 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.\nfunction dataToAlgoliaObject(data_points) {\n  var algoliaObjects = [];\n  for (var i = 0; i &lt; data_points.length; i++) {\n    var data_point = data_points[i];\n    var algoliaObject = {\n      objectID: data_point.objectID,\n      name: data_point.name,\n      rating: data_point.rating,\n      image_path: data_point.image_path,\n      alternative_name: data_point.alternative_name,\n    };\n    algoliaObjects.push(algoliaObject);\n  }\n\n  return algoliaObjects;\n}\nDeuxième étape : Maintenant que nous avons créé nos objets, ils sont prêts à être envoyés à Algolia !\nChangeons quelques trucs dans notre fonction indexData. Nous pouvons chaîner notre appel avec un .then grâce la structure de notre promesse axios et utiliser async et await pour nous assurer que tout se passe bien pendant l’envoi de nos données.\nfunction indexData(data_url) {\n  return axios\n    .get(data_url, {})\n    .then((response) =&gt; {\n      return dataToAlgoliaObject(response.data);\n    })\n    .then(async (response) =&gt; {\n      await sendDataToAlgolia(response);\n      return;\n    })\n    .then(function (response) {\n      return;\n    })\n    .catch(function (error) {\n      console.warn(error);\n    });\n}\nEnvoi des données à Algolia\nÉcrivons maintenant la fonction sendDataToAlgolia. C’est le moment où nous allons avoir besoin des clés que nous avons stockées auparavant dans notre fichier .env. 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.\nconst algoliaClient = algoliasearch(\n  process.env.ALGOLIA_APP_ID,\n  process.env.ALGOLIA_ADMIN_API_KEY\n);\nconst algoliaIndex = algoliaClient.initIndex(\"movie-actors\");\n\nfunction sendDataToAlgolia(algoliaObjects) {\n  return new Promise((resolve, reject) =&gt; {\n    algoliaIndex.addObjects(algoliaObjects, (err, content) =&gt; {\n      if (err) reject(err);\n      resolve();\n    });\n  });\n}\nConfiguration des paramètres\nNous 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 beaucoup d’options mais tenons nous en pour le moment aux options de base :\n\nsearchableAttributes: listez ce que vous voulez pouvoir rechercher dans l’objet Algolia que vous avez crée\nattributesToHighlight: mettre en surbrillance le champ recherché\ncustomRanking: choisissez la façon donc vous voulez afficher vos données, desc() ou asc()\nattributesToRetrieve: les attributs à afficher dans les résultats de recherche\n\nasync function configureAlgoliaIndex() {\n  algoliaIndex.setSettings({\n    searchableAttributes: [\"name\"],\n    attributesToHighlight: [\"name\"],\n    customRanking: [\"desc(rating)\"],\n    attributesToRetrieve: [\"name\", \"rating\", \"image_path\"],\n  });\n}\nAjoutons maintenant cette fonction, une fois l’envoi de notre index correctement effectué.\nfunction indexData(data_url) {\n  return axios\n    .get(data_url, {})\n    .then((response) =&gt; {\n      return dataToAlgoliaObject(response.data);\n    })\n    .then(async (response) =&gt; {\n      await sendDataToAlgolia(response);\n      return;\n    })\n    .then(async () =&gt; {\n      await configureAlgoliaIndex();\n      return;\n    })\n    .then(function (response) {\n      return;\n    })\n    .catch(function (error) {\n      console.warn(error);\n    });\n}\nWaouh, 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.\nConnecter le front-end\nAlgolia a ce qu'on appelle des widgets, 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.\nNous allons commencer par créer une instance d’instantsearch 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.\n$(document).ready(function () {\n  var instantsearch = window.instantsearch;\n\n  \/\/ création d’une instance d’instantsearch\n  \/\/ avec notre identifiant d’application et notre clef d’API\n  var search = instantsearch({\n    appId: Cookies.get(\"app_id\"),\n    apiKey: Cookies.get(\"search_api_key\"),\n    indexName: Cookies.get(\"index_name\"),\n    urlSync: true,\n    searchParameters: {\n      hitsPerPage: 3,\n    },\n  });\n});\nConnectons maintenant notre input de recherche à notre code HTML pour que les gens aient une barre de recherche.\nsearch.addWidget(\n  instantsearch.widgets.searchBox({\n    container: \"#search-box\",\n    placeholder: \"Rechercher vos acteurs préférés\",\n  })\n);\nMaintenant, nous voulons ajouter les résultats provenant de nos données, et retourner ce que nous voulons afficher.\nsearch.addWidget(\n  instantsearch.widgets.hits({\n    container: \"#hits\",\n    hitsPerPage: 12,\n    templates: {\n      empty: `&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`,\n      item: function (hit) {\n        try {\n          return `\n              &lt;div class=\"col-md-4\" style=\"text-align: center;\"&gt;\n                &lt;p&gt;\n                  &lt;h3 class=\"hit-text\"&gt;${hit._highlightResult.name.value}&lt;\/h3&gt;\n                  &lt;img src=\"https:\/\/image.tmdb.org\/t\/p\/w45\/${hit.image_path}\" height=\"50\" width=\"50\"&gt;\n                &lt;\/p&gt;\n                &lt;p&gt;\n                  Rating: ⭐️ ${hit.rating}\n                &lt;\/p&gt;\n              &lt;\/div&gt;\n            `;\n        } catch (e) {\n          console.warn(\"Couldn't render hit\", hit, e);\n          return \"\";\n        }\n      },\n    },\n  })\n);\nUne 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.\nsearch.addWidget(\n  instantsearch.widgets.pagination({\n    container: \"#pagination\",\n  })\n);\nEt enfin pour terminer… lançons la recherche ! Cela va permettre de tout instancier dans votre page.\nsearch.start();\nNaturellement, si vous voulez vous épargner tout ce travail manuel, vous pouvez allez voir notre application pour démarrer rapidement sur Glitch, la remixer et vous aurez tout ce code et une application basique qui tourne en moins de 5 minutes.\n😉 J'espère que cette lecture vous a plu et vous aura été utile !",
      "content_html": "<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"obtenir-vos-clefs-d-api\">Obtenir vos clefs d’API</h2>\n<figure>\n<picture title=\"Les différentes clés d’API d’Algolia\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Les différentes clés d’API d’Algolia</figcaption>\n</figure>\n<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>\n<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>\n<h2 id=\"connecter-votre-source-de-donnees\">Connecter votre source de données</h2>\n<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>\n<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>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">indexData</span>(<span class=\"hljs-params\">data_url</span>) </span>{\n  <span class=\"hljs-keyword\">return</span> axios\n    .get(data_url, {})\n    .then(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">response</span>) </span>{\n      <span class=\"hljs-built_in\">console</span>.log(response.data[<span class=\"hljs-number\">0</span>]);\n      <span class=\"hljs-keyword\">return</span>;\n    })\n    .catch(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">error</span>) </span>{\n      <span class=\"hljs-built_in\">console</span>.warn(error);\n    });\n}</code></pre>\n<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>\n<h2 id=\"preparer-les-donnees-pour-algolia\">Préparer les données pour Algolia</h2>\n<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>\n<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>\n<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>{\n  <span class=\"hljs-keyword\">return</span> axios\n    .get(data_url, {})\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">response</span>) =&gt;</span> {\n      <span class=\"hljs-keyword\">return</span> dataToAlgoliaObject(response.data);\n    })\n    .then(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">response</span>) </span>{\n      <span class=\"hljs-keyword\">return</span>;\n    })\n    .catch(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">error</span>) </span>{\n      <span class=\"hljs-built_in\">console</span>.warn(error);\n    });\n}</code></pre>\n<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>\n<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>{\n  <span class=\"hljs-keyword\">var</span> algoliaObjects = [];\n  <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++) {\n    <span class=\"hljs-keyword\">var</span> data_point = data_points[i];\n    <span class=\"hljs-keyword\">var</span> algoliaObject = {\n      <span class=\"hljs-attr\">objectID</span>: data_point.objectID,\n      <span class=\"hljs-attr\">name</span>: data_point.name,\n      <span class=\"hljs-attr\">rating</span>: data_point.rating,\n      <span class=\"hljs-attr\">image_path</span>: data_point.image_path,\n      <span class=\"hljs-attr\">alternative_name</span>: data_point.alternative_name,\n    };\n    algoliaObjects.push(algoliaObject);\n  }\n\n  <span class=\"hljs-keyword\">return</span> algoliaObjects;\n}</code></pre>\n<p><em>Deuxième étape :</em> Maintenant que nous avons créé nos objets, ils sont prêts à être envoyés à Algolia !</p>\n<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>\n<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>{\n  <span class=\"hljs-keyword\">return</span> axios\n    .get(data_url, {})\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">response</span>) =&gt;</span> {\n      <span class=\"hljs-keyword\">return</span> dataToAlgoliaObject(response.data);\n    })\n    .then(<span class=\"hljs-keyword\">async</span> (response) =&gt; {\n      <span class=\"hljs-keyword\">await</span> sendDataToAlgolia(response);\n      <span class=\"hljs-keyword\">return</span>;\n    })\n    .then(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">response</span>) </span>{\n      <span class=\"hljs-keyword\">return</span>;\n    })\n    .catch(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">error</span>) </span>{\n      <span class=\"hljs-built_in\">console</span>.warn(error);\n    });\n}</code></pre>\n<h2 id=\"envoi-des-donnees-a-algolia\">Envoi des données à Algolia</h2>\n<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>\n<pre><code class=\"language-javascript hljs javascript\"><span class=\"hljs-keyword\">const</span> algoliaClient = algoliasearch(\n  process.env.ALGOLIA_APP_ID,\n  process.env.ALGOLIA_ADMIN_API_KEY\n);\n<span class=\"hljs-keyword\">const</span> algoliaIndex = algoliaClient.initIndex(<span class=\"hljs-string\">\"movie-actors\"</span>);\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">sendDataToAlgolia</span>(<span class=\"hljs-params\">algoliaObjects</span>) </span>{\n  <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> {\n    algoliaIndex.addObjects(algoliaObjects, (err, content) =&gt; {\n      <span class=\"hljs-keyword\">if</span> (err) reject(err);\n      resolve();\n    });\n  });\n}</code></pre>\n<h2 id=\"configuration-des-parametres\">Configuration des paramètres</h2>\n<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>\n<ul>\n<li><em>searchableAttributes</em>: listez ce que vous voulez pouvoir rechercher dans l’objet Algolia que vous avez crée</li>\n<li><em>attributesToHighlight</em>: mettre en surbrillance le champ recherché</li>\n<li><em>customRanking</em>: choisissez la façon donc vous voulez afficher vos données, <code>desc()</code> ou <code>asc()</code></li>\n<li><em>attributesToRetrieve</em>: les attributs à afficher dans les résultats de recherche</li>\n</ul>\n<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>{\n  algoliaIndex.setSettings({\n    <span class=\"hljs-attr\">searchableAttributes</span>: [<span class=\"hljs-string\">\"name\"</span>],\n    <span class=\"hljs-attr\">attributesToHighlight</span>: [<span class=\"hljs-string\">\"name\"</span>],\n    <span class=\"hljs-attr\">customRanking</span>: [<span class=\"hljs-string\">\"desc(rating)\"</span>],\n    <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>],\n  });\n}</code></pre>\n<p>Ajoutons maintenant cette fonction, une fois l’envoi de notre index correctement effectué.</p>\n<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>{\n  <span class=\"hljs-keyword\">return</span> axios\n    .get(data_url, {})\n    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">response</span>) =&gt;</span> {\n      <span class=\"hljs-keyword\">return</span> dataToAlgoliaObject(response.data);\n    })\n    .then(<span class=\"hljs-keyword\">async</span> (response) =&gt; {\n      <span class=\"hljs-keyword\">await</span> sendDataToAlgolia(response);\n      <span class=\"hljs-keyword\">return</span>;\n    })\n    .then(<span class=\"hljs-keyword\">async</span> () =&gt; {\n      <span class=\"hljs-keyword\">await</span> configureAlgoliaIndex();\n      <span class=\"hljs-keyword\">return</span>;\n    })\n    .then(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">response</span>) </span>{\n      <span class=\"hljs-keyword\">return</span>;\n    })\n    .catch(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">error</span>) </span>{\n      <span class=\"hljs-built_in\">console</span>.warn(error);\n    });\n}</code></pre>\n<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>\n<h2 id=\"connecter-le-front-end\">Connecter le front-end</h2>\n<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>\n<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>\n<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>{\n  <span class=\"hljs-keyword\">var</span> instantsearch = <span class=\"hljs-built_in\">window</span>.instantsearch;\n\n  <span class=\"hljs-comment\">// création d’une instance d’instantsearch</span>\n  <span class=\"hljs-comment\">// avec notre identifiant d’application et notre clef d’API</span>\n  <span class=\"hljs-keyword\">var</span> search = instantsearch({\n    <span class=\"hljs-attr\">appId</span>: Cookies.get(<span class=\"hljs-string\">\"app_id\"</span>),\n    <span class=\"hljs-attr\">apiKey</span>: Cookies.get(<span class=\"hljs-string\">\"search_api_key\"</span>),\n    <span class=\"hljs-attr\">indexName</span>: Cookies.get(<span class=\"hljs-string\">\"index_name\"</span>),\n    <span class=\"hljs-attr\">urlSync</span>: <span class=\"hljs-literal\">true</span>,\n    <span class=\"hljs-attr\">searchParameters</span>: {\n      <span class=\"hljs-attr\">hitsPerPage</span>: <span class=\"hljs-number\">3</span>,\n    },\n  });\n});</code></pre>\n<p>Connectons maintenant notre <em>input</em> de recherche à notre code HTML pour que les gens aient une barre de recherche.</p>\n<pre><code class=\"language-javascript hljs javascript\">search.addWidget(\n  instantsearch.widgets.searchBox({\n    <span class=\"hljs-attr\">container</span>: <span class=\"hljs-string\">\"#search-box\"</span>,\n    <span class=\"hljs-attr\">placeholder</span>: <span class=\"hljs-string\">\"Rechercher vos acteurs préférés\"</span>,\n  })\n);</code></pre>\n<p>Maintenant, nous voulons ajouter les résultats provenant de nos données, et retourner ce que nous voulons afficher.</p>\n<pre><code class=\"language-javascript hljs javascript\">search.addWidget(\n  instantsearch.widgets.hits({\n    <span class=\"hljs-attr\">container</span>: <span class=\"hljs-string\">\"#hits\"</span>,\n    <span class=\"hljs-attr\">hitsPerPage</span>: <span class=\"hljs-number\">12</span>,\n    <span class=\"hljs-attr\">templates</span>: {\n      <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>,\n      <span class=\"hljs-attr\">item</span>: <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">hit</span>) </span>{\n        <span class=\"hljs-keyword\">try</span> {\n          <span class=\"hljs-keyword\">return</span> <span class=\"hljs-string\">`\n              &lt;div class=\"col-md-4\" style=\"text-align: center;\"&gt;\n                &lt;p&gt;\n                  &lt;h3 class=\"hit-text\"&gt;<span class=\"hljs-subst\">${hit._highlightResult.name.value}</span>&lt;/h3&gt;\n                  &lt;img src=\"https://image.tmdb.org/t/p/w45/<span class=\"hljs-subst\">${hit.image_path}</span>\" height=\"50\" width=\"50\"&gt;\n                &lt;/p&gt;\n                &lt;p&gt;\n                  Rating: ⭐️ <span class=\"hljs-subst\">${hit.rating}</span>\n                &lt;/p&gt;\n              &lt;/div&gt;\n            `</span>;\n        } <span class=\"hljs-keyword\">catch</span> (e) {\n          <span class=\"hljs-built_in\">console</span>.warn(<span class=\"hljs-string\">\"Couldn't render hit\"</span>, hit, e);\n          <span class=\"hljs-keyword\">return</span> <span class=\"hljs-string\">\"\"</span>;\n        }\n      },\n    },\n  })\n);</code></pre>\n<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>\n<pre><code class=\"language-javascript hljs javascript\">search.addWidget(\n  instantsearch.widgets.pagination({\n    <span class=\"hljs-attr\">container</span>: <span class=\"hljs-string\">\"#pagination\"</span>,\n  })\n);</code></pre>\n<p>Et enfin pour terminer… lançons la recherche ! Cela va permettre de tout instancier dans votre page.</p>\n<pre><code class=\"language-javascript hljs javascript\">search.start();</code></pre>\n<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>\n<p>😉 J'espère que cette lecture vous a plu et vous aura été utile !</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/03/17/premier-meetup-jamstack-toulouse/",
      "url": "https://jamstatic.fr/2018/03/17/premier-meetup-jamstack-toulouse/",
      "title": "Meetup Jamstack Toulouse",
      "summary": "Deux présentations de Netlify et Algolia lors premier meetup Jamstack en France.",
      "date_published": "2018-03-17T08:24:53+00:00","content_text": "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 developer experience et les bénéfices apportés par ces deux services de qualité.\n\n\n\n\n\n\nPhoto : Nicolas Manaud\n\nLors 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é.\nMaking platforms promote performance\nLa présentation de Phil Hawksworth, 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.\n\n\n\nPour 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 officiel. Et cette façon de travailler est rendue très accessible par Netlify, 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é.\nPhil 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.\n\n\n\n\n\n\nLes premiers prototypes du site de Netlify sont encore accessibles en ligne.\n\nUn 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 split-testing.\nPour 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.\nAjouter une recherche sur un site statique\n\n\n\nMartyn Davies, nouvellement arrivé chez Algolia, 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 Jekyll 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.\nC’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 jekyll-algolia développé par Tim Carry, passé ingénieur open-source à plein temps chez Algolia.\nDans 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 Copier-Coller driven development.\nUne 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 les exemples documentés sur le site d’Algolia 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.\nUne chouette soirée\nDeux présentations rondemment menées donc, qui ont été bien appréciées par les personnes présentes.\nMaxime Thirouin le développeur de Phenomic 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 code source ouvert du paquet atomic-algolia développé par Chris Macrae de Forestry. C’est beau l’open source.\nNous 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. :)\nEt grâce au soutien de Front-Commerce, 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 !",
      "content_html": "<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>\n<figure>\n<picture title=\"Photo : Nicolas Manaud\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Photo : <a href=\"https://twitter.com/nmanaud/status/974957331279695872\" target=\"_blank\" rel=\"noopener noreferrer\">Nicolas Manaud</a></figcaption>\n</figure>\n<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>\n<h2 id=\"making-platforms-promote-performance\">Making platforms promote performance</h2>\n<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>\n<p><div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div></p>\n<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>\n<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>\n<figure>\n<picture title=\"Les premiers prototypes du site de Netlify sont encore accessibles en ligne.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Les premiers prototypes du site de Netlify sont encore accessibles en ligne.</figcaption>\n</figure>\n<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>\n<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>\n<h2 id=\"ajouter-une-recherche-sur-un-site-statique\">Ajouter une recherche sur un site statique</h2>\n<p><div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div></p>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"une-chouette-soiree\">Une chouette soirée</h2>\n<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>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/03/13/un-site-statique-avec-des-composants-grace-a-nunjucks/",
      "url": "https://jamstatic.fr/2018/03/13/un-site-statique-avec-des-composants-grace-a-nunjucks/",
      "title": "Un site statique avec des composants à l’aide de Nunjucks",
      "summary": "Apprenez à construire un site statique avec des composants à l’aide d’un préprocesseur HTML.",
      "date_published": "2018-03-13T19:21:48+00:00","content_text": "La philosophie de la génération de site statique à l’aide d’un langage de\ntemplating et d’un langage de balisage léger comme Markdown continue d’être\ndéclinée à l’envi. Le même principe est repris ici par Chris Coyier, le créateur\nde CodePen que les développeurs front connaissent bien, pour la création\nd’un site qui liste quelques-unes des possibilités offertes\npar ce que nous appelons plus globalement la Jamstack et\nqui est ici désigné sous le nom de serverless — une des nombreuses composantes\nde ces architectures découplées.\nUn cas d’étude très simple qui pourrait vous donner des idées. Aucune\nconfiguration puisque c'est un service en ligne payant qui est utilisé ici, mais\non pourrait tout aussi bien utiliser le générateur open source\nEleventy par exemple, qui utilise aussi\nNunjucks.\nIl est de plus en plus courant de nos jours de bâtir des sites avec des\ncomposants et c'est une très bonne idée. Plutôt que de construire les pages les\nunes après les autres, nous développons un système de composants (comme un\nformulaire de recherche, un carte d’article, un menu, un pied de page) et nous\nassemblons le site avec ces composants.\nDes frameworks JavaScript comme React ou Vue reposent en grande partie sur ce\nprincipe. Mais ce n'est pas parce que vous n'utilisez pas de JavaScript côté\nclient pour développer votre site que vous devez renoncer à l’idée d’utiliser\ndes composants. En utilisant un préprocesseur HTML, nous pouvons monter un site\nstatique et bénéficier également de le possibilité d’abstraire notre site et son\ncontenu sous forme de composants réutilisables.\nLes sites statiques font fureur actuellement, et à juste titre, car ils sont\nrapides, sécurisés et pas cher à héberger. Croyez-le ou pas mais même Smashing\nMagazine est un site statique !\nRegardons de plus près un site que j'ai monté récemment à l’aide de cette\ntechnique. J'ai utilisé CodePen Projects pour\nle développer qui supporte Nunjucks comme\npréprocesseur, ce qui est parfait pour faire le job.\nUn site de quatre pages avec un entête, une navigation et un pied de page consistants\nC’est un microsite. Nul besoin de passer\npar CMS capable de gérer des centaines de pages, ni de JavaScript pour ajouter\nde l’interaction. Par contre il faut qu'une poignée de pages partage la même\nmise en page.\n\n\n\n\n\nHTML n'apporte pas encore de réponse à ce problème. Nous avons besoin de pouvoir\nfaire des imports. Les langages comme PHP permettent cela avec\n&lt;?php include \"header.php\"; ?&gt;, mais PHP n'est pas disponible (à dessein) chez\nles hébergeurs de sites statiques et HTML ne peut encore rien pour nous.\nHeureusement nous pouvons préprocesser nos inclusions à l’aide d’un langage de\ntemplating comme Nunjucks.\n\n\n\n\n\nCela fait parfaitement sens ici de créer un gabarit de page, qui inclus des\nmorceaux de HTML pour le haut de page, la navigation et le pied de page. Le\nconcept de blocs de Nunjucks nous permet d’insérer du contenu à cet endroit\nlorsque nous utilisons le gabarit de page.\n&lt;head&gt;\n  &lt;meta charset=\"UTF-8\" \/&gt;\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/&gt;\n  &lt;title&gt;The Power of Serverless&lt;\/title&gt;\n  &lt;link rel=\"stylesheet\" href=\"\/styles\/style.processed.css\" \/&gt;\n&lt;\/head&gt;\n\n&lt;body&gt;\n  {% include \".\/template-parts\/_header.njk\" %} {% include\n  \".\/template-parts\/_nav.njk\" %} {% block content %} {% endblock %} {% include\n  \".\/template-parts\/_footer.njk\" %}\n&lt;\/body&gt;\nVous remarquerez que les fichiers inclus sont commencent par un tiret bas et\npossèdent l’extension .njk. Ce n'est pas obligatoire, ils pourraient se nommer\nheader.html ou icons.svg mais ils sont nommés ainsi car premièrement c'est\nune convention de nommage qu'on rencontre souvent pour les fichiers partiels.\nDans CodePen, cela signifie qu'ils ne seront pas compilés tous seuls, et\ndeuxièmement utiliser l’extention .njk nous permettra de faire plus de choses\navec Nunjucks si besoin.\nRien de spécial dans ces morceaux de fichiers. Ce sont simplement des petits\nbouts de HTML destinés à être utilisés sur toutes nos pages.\n&lt;footer&gt;\n  &lt;p&gt;Ceci est un simple pied de page, les gens. Circulez, y’a rien à voir.&lt;\/p&gt;\n&lt;\/footer&gt;\nDe cette manière, un simple changement dans ces fichiers et il sera appliqué sur\ntoutes nos pages.\nUtiliser un seul gabarit pour nos pages\nMaintenant nous pouvons créer un fichier pour chacune de nos pages. Commençons\nquand même par index.njk , qui sera automatiquement traité pour créer un\nfichier index.html dans CodePen project à chaque enregistrement.\n\n\n\n\n\nVoici ce que nous pourrions écrirer dans le fichier index.njk pour appliquer\nle gabarit de page et ajouter du contenu dans le bloc principal :\n{% extends \"_layout.njk\" %}\n\n{% block content %}\n&lt;h1&gt;Bonjour, monde!&lt;\/h1&gt;\n{% endblock %}\nÇa suffit pour avoir une page d’accueil fonctionnelle. Sympa ! On peut faire\npareil pour chacune des autres pages, le contenu du bloc sera simplement\ndifférent, et nous aurons un petit site de quatre pages facile à maintenir.\n\n\n\n\n\nEntre nous soit dit, je ne suis pas persuadé que ces petits morceaux\nréutilisables soient des composants à proprement parlé. Nous sommes simplement\nefficients et nous découpons notre gabarit en petits morceaux. Je pense qu'un\ncomposant est plutôt quelque chose qui accepte des données en entrée et génère\nune version unique de lui-même avec ces données. Nous allons y venir.\nRendre la navigation active\nMaintenant que nous avons des morceaux de HTML répétés à l’identique sur nos\npages, pouvons-nous appliquer un style CSS unique aux entrées indiviudelles de\nla navigation pour identifier la page courante ? Nous pourrions le faire avec\nJavaScript en regardant la valeur retournée par window.location par exemple,\nmais nous pouvons nous passer de JavaScript pour cela. Le truc c'est d’ajouter\nune class unique pour chaque page sur l’élément &lt;body&gt; et de la styler avec\nCSS.\nDans notre fichier _layout.njk nous allons générer un nom de classe à l’aide\nd’une variable :\n&lt;body class=\"{{ body_class }}\"&gt;&lt;\/body&gt;\n\n\n\nEt avant d’appliquer le gabarit sur chaque page, nous définissons cette variable\n\n\n\n\n\n{% set body_class = \"home\" %} {% extends \"_layout.njk\" %}\nImaginons que notre navigation soit structurée de la sorte :\n&lt;nav class=\"site-nav\"&gt;\n  &lt;ul&gt;\n    &lt;li class=\"nav-home\"&gt;\n      &lt;a href=\"\/\"&gt; Home &lt;\/a&gt;\n      …\n    &lt;\/li&gt;\n  &lt;\/ul&gt;\n&lt;\/nav&gt;\nNous pouvons maintenant cibler ce lien et lui appliquer un style spécifique en\nécrivant :\nbody.home .nav-home a,\nbody.services .nav-services a {\n  \/* continue matching classes for all pages… *\/\n  \/* unique active state styling *\/\n}\n\nOh, c'est quoi ces icônes ? Ce sont simplement des fichiers .svg déposés\ndans un dossier et inclus de la sorte :\n{% include \"..\/icons\/cloud.svg\" %}\nCela me permet de les styler ainsi :\nsvg {\n  fill: white;\n}\nEn partant du principe que les éléments SVG du fichier n'ont pas déjà un\nattribut fill de défini.\nRédiger du contenu en Markdown\nLa page d’accueil de mon site affiche un gros bloc de contenu. Je pourrais\nécrire et maintenir cela en HTML, mais il est parfois bon de\nlaisser ce genre de chose à Markdown.\nMarkdown est plus léger et un peu plus lisible lorsqu'il y a beaucoup de texte.\nRien de plus simple dans CodePen Projects. Je crée un fichier avec l’extension\n.md qui sera automatiquement transformé en HTML avant d’être inclus dans le\nfichier index.njk.\n\n{% block content %}\n&lt;main class=\"centered-text-column\"&gt;{% include \"content\/about.html\" %}&lt;\/main&gt;\n{% endblock %}\nDévelopper de vrais composants\nPartons du principe que les composants sont des modules réutilisables à qui on\ninjecte des données lors de leur création. Dans des frameworks comme Vue, vous\nutiliseriez des\nfichiers uniques par composants,\nqui sont des morceaux isolés de HTML modelé, de CSS au périmètre limité et de\nJavaScript spécifique au composant. C’est super cool, mais notre microsite n'a\npas besoin de quelque chose d’aussi sophistiqué.\nNous avons besoin de créer des \"cartes\" qui utilisent un modèle simple, on peut\npar exemple faire quelque chose comme :\n\n\n\n\n\nPour créer un tel composant dans Nunjucks, il faut utilise ce qu'on appelle des\nMacros. Les Macros sont\nd’une simplicité exquise. C’est comme des fonctions pour HTML !\n{% macro card(title, content) %}\n&lt;div class=\"card\"&gt;\n  &lt;h2&gt;{{ title }}&lt;\/h2&gt;\n  &lt;p&gt;{{ content }}&lt;\/p&gt;\n&lt;\/div&gt;\n{% endmacro %}\nPuis vous les appelez comme bon vous semble :\n{{ card('My Module', 'Lorem ipsum whatever.') }}\nL'idée générale est de séparer les données et le balisage. Cela nous donne\ndes bénéfices concrets et assez clairs :\n\nSi nous devons changer le HTML, nous pouvons le faire dans la macro et le changement sera reporté partout où la macro est utilisée.\nLa donnée n'est pas mélangée avec le balisage\nLa donnée pourrait venir de n'importe où ! Nous pouvons passer la donnée\ndirectement lors de l’appel comme nous l’avons fait ci-dessus. Ou bien nous\npouvons référencer des données en JSON et boucler dessus. Je suis sûr qu'on\npourrait mettre en place un système dans lequel des données JSON proviennent\nd’un CMS headless, d’un processus de\ngénération, d’une fonction serverless, d’une tâche cron ou de ce que vous\nvoulez.\n\nMaintenant que nous avons juste ce dont nous avons besoin, des cartes répétables\nqui combinent des données et du balisage :\n\n\n\n\n\nCréer autant de composants que vous voulez\nVous pouvez partir de ce principe et l’appliquer à l’envi. Imaginez par exemple\nque Bootstrap est essentiellement des bouts des CSS qui suivent des patrons en\nHTML qui définissent leur utilisation. Vous pourriez transformer tous les\npatrons du framework en macro et les appeler en fonction de vos besoins. L'idée\nest celle d’un\nframework de composants.\nVous pouvez imbriquer les composants si ça vous chante, et tendre vers quelque\nchose qui ressemble à la philosophie du\ndesign atomique.\nNunjucks offre également une couche de logique, qui fait que vous pouvez créer\ndes composants conditionnels et des variations en passant différents paramètres.\nDans le petit site que j'ai crée, j'ai\ncréé une autre macro pour la section idées du site car les données utilisées et\nle design de la carte sont légèrement différents.\n\n\n\n\n\nUn avis rapide sur les sites statiques\nSi la plupart des sites pourraient bénéficier d’une architecture à base de\ncomposants, tous les sites ne sont pas faits pour être statiques. Je travaille\nsur beaucoup de sites pour lesquels utiliser un langage côté serveur est\napproprié et utile.\nUn de mes sites, CSS-Tricks, propose des trucs comme\nun compte utilisateur avec un système de permissions complexe : forums,\ncommentaires, ecommerce. Bien que toutes ces fonctionnalités n'empêchent pas de\ntravailler en statique, je suis souvent bien content d’avoir une base de données\net des langages côté serveur pour travailler. Cela me permet de développer ce\nsont j'ai besoin et de tout avoir au même endroit.\nAllez de l’avant et passer au statique\nRappelez-vous qu'un des bénéfices de développer le site comme nous l’avons fait\ndans cet article est qu'au final nous obtenons quelques fichiers statiques.\nFaciles à héberger, performants et sécurisés. Pourtant, nous avons pu travailler\nde manière sympa et efficace. Ce site sera simple à mettre à jour par la suite…\n\nLe projet final est un microsite nommé Le pouvoir du Serverless pour les\ndéveloppeurs Front-End (https:\/\/thepowerofserverless.info\/).\nL'hébergement de fichier statique fait partie selon moi du mouvement\nserverless.\nTout le code est visible (et vous pouvez même en faire une copie pour vous)\ndirectement dans CodePen.\nIl est maintenu, généré et\nhébergé entièrement sur\nCodePen à l’aide de CodePen Projects.\nCodePen Projects s'occupe de toute la partie\nNunjucks dont nous avons parlé ici,\nainsi que de la compilation Sass et de l’hébergement des images, que j'ai\nutilisé pour le site. Vous pourriez faire la même chose avec par exemple un\nprocess de génération basé sur Gulp voire Grunt par exemple.\nVoici un modèle de départ pour un tel projet\nque vous pourriez utiliser.\n",
      "content_html": "<aside class=\"note note-intro\"><p>La philosophie de la génération de site statique à l’aide d’un langage de\ntemplating et d’un langage de balisage léger comme Markdown continue d’être\ndéclinée à l’envi. Le même principe est repris ici par Chris Coyier, le créateur\nde CodePen que les développeurs front connaissent bien, pour la création\nd’<a href=\"https://thepowerofserverless.info\" target=\"_blank\" rel=\"noopener noreferrer\">un site qui liste quelques-unes des possibilités offertes</a>\npar ce que nous appelons plus globalement la <a href=\"/categories/jamstack\">Jamstack</a> et\nqui est ici désigné sous le nom de <em>serverless</em> — une des nombreuses composantes\nde ces architectures découplées.\nUn cas d’étude très simple qui pourrait vous donner des idées. Aucune\nconfiguration puisque c'est un service en ligne payant qui est utilisé ici, mais\non pourrait tout aussi bien utiliser le générateur open source\n<a href=\"/categories/eleventy\">Eleventy</a> par exemple, qui utilise aussi\n<a href=\"https://mozilla.github.io/nunjucks/\" target=\"_blank\" rel=\"noopener noreferrer\">Nunjucks</a>.</p></aside>\n<p>Il est de plus en plus courant de nos jours de bâtir des sites avec des\ncomposants et c'est une très bonne idée. Plutôt que de construire les pages les\nunes après les autres, nous développons un système de composants (comme un\nformulaire de recherche, un carte d’article, un menu, un pied de page) et nous\nassemblons le site avec ces composants.</p>\n<p>Des frameworks JavaScript comme React ou Vue reposent en grande partie sur ce\nprincipe. Mais ce n'est pas parce que vous n'utilisez pas de JavaScript côté\nclient pour développer votre site que vous devez renoncer à l’idée d’utiliser\ndes composants. En utilisant un préprocesseur HTML, nous pouvons monter un site\nstatique et bénéficier également de le possibilité d’abstraire notre site et son\ncontenu sous forme de composants réutilisables.</p>\n<p>Les sites statiques font fureur actuellement, et à juste titre, car ils sont\nrapides, sécurisés et pas cher à héberger. Croyez-le ou pas mais même Smashing\nMagazine est un site statique !</p>\n<p>Regardons de plus près un site que j'ai monté récemment à l’aide de cette\ntechnique. J'ai utilisé <a href=\"https://codepen.io/pro/projects\" target=\"_blank\" rel=\"noopener noreferrer\">CodePen Projects</a> pour\nle développer qui supporte <a href=\"https://mozilla.github.io/nunjucks/\" target=\"_blank\" rel=\"noopener noreferrer\">Nunjucks</a> comme\npréprocesseur, ce qui est parfait pour faire le job.</p>\n<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>\n<p><a href=\"https://thepowerofserverless.info/\" target=\"_blank\" rel=\"noopener noreferrer\">C’est un microsite</a>. Nul besoin de passer\npar CMS capable de gérer des centaines de pages, ni de JavaScript pour ajouter\nde l’interaction. Par contre il faut qu'une poignée de pages partage la même\nmise en page.</p>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<p>HTML n'apporte pas encore de réponse à ce problème. Nous avons besoin de pouvoir\nfaire des <em>imports</em>. Les langages comme PHP permettent cela avec\n<code>&lt;?php include \"header.php\"; ?&gt;</code>, mais PHP n'est pas disponible (à dessein) chez\nles hébergeurs de sites statiques et HTML ne peut encore rien pour nous.\nHeureusement nous pouvons préprocesser nos inclusions à l’aide d’un langage de\ntemplating comme <a href=\"https://mozilla.github.io/nunjucks/\" target=\"_blank\" rel=\"noopener noreferrer\">Nunjucks</a>.</p>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<p>Cela fait parfaitement sens ici de créer un gabarit de page, qui inclus des\nmorceaux de HTML pour le haut de page, la navigation et le pied de page. Le\nconcept de blocs de Nunjucks nous permet d’insérer du contenu à cet endroit\nlorsque nous utilisons le gabarit de page.</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span>\n  <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>\n  <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>\n  <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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span>\n  {% include \"./template-parts/_header.njk\" %} {% include\n  \"./template-parts/_nav.njk\" %} {% block content %} {% endblock %} {% include\n  \"./template-parts/_footer.njk\" %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span></code></pre>\n<p>Vous remarquerez que les fichiers inclus sont commencent par un tiret bas et\npossèdent l’extension <code>.njk</code>. Ce n'est pas obligatoire, ils pourraient se nommer\n<code>header.html</code> ou <code>icons.svg</code> mais ils sont nommés ainsi car premièrement c'est\nune convention de nommage qu'on rencontre souvent pour les fichiers partiels.\nDans CodePen, cela signifie qu'ils ne seront pas compilés tous seuls, et\ndeuxièmement utiliser l’extention <code>.njk</code> nous permettra de faire plus de choses\navec Nunjucks si besoin.</p>\n<p>Rien de spécial dans ces morceaux de fichiers. Ce sont simplement des petits\nbouts de HTML destinés à être utilisés sur toutes nos pages.</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">footer</span>&gt;</span>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">footer</span>&gt;</span></code></pre>\n<p>De cette manière, un simple changement dans ces fichiers et il sera appliqué sur\ntoutes nos pages.</p>\n<h3 id=\"utiliser-un-seul-gabarit-pour-nos-pages\">Utiliser un seul gabarit pour nos pages</h3>\n<p>Maintenant nous pouvons créer un fichier pour chacune de nos pages. Commençons\nquand même par <code>index.njk</code> , qui sera automatiquement traité pour créer un\nfichier <code>index.html</code> dans CodePen project à chaque enregistrement.</p>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<p>Voici ce que nous pourrions écrirer dans le fichier <code>index.njk</code> pour appliquer\nle gabarit de page et ajouter du contenu dans le bloc principal :</p>\n<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\">\n\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">block</span></span> content %}</span><span class=\"xml\">\n<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>\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endblock</span></span> %}</span></code></pre>\n<p>Ça suffit pour avoir une page d’accueil fonctionnelle. Sympa ! On peut faire\npareil pour chacune des autres pages, le contenu du bloc sera simplement\ndifférent, et nous aurons un petit site de quatre pages facile à maintenir.</p>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<p>Entre nous soit dit, je ne suis pas persuadé que ces petits morceaux\nréutilisables soient des <em>composants</em> à proprement parlé. Nous sommes simplement\nefficients et nous découpons notre gabarit en petits morceaux. Je pense qu'un\ncomposant est plutôt quelque chose qui accepte des données en entrée et génère\nune version unique de lui-même avec ces données. Nous allons y venir.</p>\n<h3 id=\"rendre-la-navigation-active\">Rendre la navigation active</h3>\n<p>Maintenant que nous avons des morceaux de HTML répétés à l’identique sur nos\npages, pouvons-nous appliquer un style CSS unique aux entrées indiviudelles de\nla navigation pour identifier la page courante ? Nous pourrions le faire avec\nJavaScript en regardant la valeur retournée par <code>window.location</code> par exemple,\nmais nous pouvons nous passer de JavaScript pour cela. Le truc c'est d’ajouter\nune <code>class</code> unique pour chaque page sur l’élément <code>&lt;body&gt;</code> et de la styler avec\nCSS.</p>\n<p>Dans notre fichier <code>_layout.njk</code> nous allons générer un nom de classe à l’aide\nd’une variable :</p>\n<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>\n<table>\n<thead>\n<tr>\n<th style=\"text-align: center;\">Et avant d’appliquer le gabarit sur chaque page, nous définissons cette variable</th>\n</tr>\n</thead>\n<tbody>\n</tbody>\n</table>\n<pre><code class=\"language-html hljs xml\">{% set body_class = \"home\" %} {% extends \"_layout.njk\" %}</code></pre>\n<p>Imaginons que notre navigation soit structurée de la sorte :</p>\n<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>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n    <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>\n      <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>\n      …\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">nav</span>&gt;</span></code></pre>\n<p>Nous pouvons maintenant cibler ce lien et lui appliquer un style spécifique en\nécrivant :</p>\n<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>,\n<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> {\n  <span class=\"hljs-comment\">/* continue matching classes for all pages… */</span>\n  <span class=\"hljs-comment\">/* unique active state styling */</span>\n}</code></pre>\n<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\">\n<p><em>Oh, c'est quoi ces icônes ?</em> Ce sont simplement des fichiers <code>.svg</code> déposés\ndans un dossier et inclus de la sorte :</p>\n<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>\n<p>Cela me permet de les styler ainsi :</p>\n<pre><code class=\"language-css hljs css\"><span class=\"hljs-selector-tag\">svg</span> {\n  <span class=\"hljs-attribute\">fill</span>: white;\n}</code></pre>\n<p>En partant du principe que les éléments SVG du fichier n'ont pas déjà un\nattribut <code>fill</code> de défini.</p>\n<h3 id=\"rediger-du-contenu-en-markdown\">Rédiger du contenu en Markdown</h3>\n<p>La page d’accueil de mon site affiche un gros bloc de contenu. Je pourrais\nécrire et maintenir cela en HTML, mais il est parfois bon de\n<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>.\nMarkdown est plus léger et un peu plus lisible lorsqu'il y a beaucoup de texte.</p>\n<p>Rien de plus simple dans CodePen Projects. Je crée un fichier avec l’extension\n<code>.md</code> qui sera automatiquement transformé en HTML avant d’être inclus dans le\nfichier <code>index.njk</code>.</p>\n<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\">\n<pre><code class=\"language-html hljs xml\">{% block content %}\n<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>\n{% endblock %}</code></pre>\n<h3 id=\"developper-de-vrais-composants\">Développer de vrais composants</h3>\n<p>Partons du principe que les composants sont des modules réutilisables à qui on\ninjecte des données lors de leur création. Dans des frameworks comme Vue, vous\nutiliseriez des\nf<a href=\"https://vuejs.org/v2/guide/single-file-components.html\" target=\"_blank\" rel=\"noopener noreferrer\">ichiers uniques par composants</a>,\nqui sont des morceaux isolés de HTML modelé, de CSS au périmètre limité et de\nJavaScript spécifique au composant. C’est super cool, mais notre microsite n'a\npas besoin de quelque chose d’aussi sophistiqué.</p>\n<p>Nous avons besoin de créer des \"cartes\" qui utilisent un modèle simple, on peut\npar exemple faire quelque chose comme :</p>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<p>Pour créer un tel composant dans Nunjucks, il faut utilise ce qu'on appelle des\n<a href=\"https://mozilla.github.io/nunjucks/templating.html\" target=\"_blank\" rel=\"noopener noreferrer\">Macros</a>. Les Macros sont\nd’une simplicité exquise. C’est comme <strong>des fonctions pour HTML</strong> !</p>\n<pre><code class=\"language-html hljs xml\">{% macro card(title, content) %}\n<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>\n  <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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n{% endmacro %}</code></pre>\n<p>Puis vous les appelez comme bon vous semble :</p>\n<pre><code class=\"language-html hljs xml\">{{ card('My Module', 'Lorem ipsum whatever.') }}</code></pre>\n<p>L'idée générale est de <strong>séparer les données et le balisage</strong>. Cela nous donne\ndes bénéfices concrets et assez clairs :</p>\n<ol>\n<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>\n<li>La donnée n'est pas mélangée avec le balisage</li>\n<li>La donnée pourrait venir de n'importe où ! Nous pouvons passer la donnée\ndirectement lors de l’appel comme nous l’avons fait ci-dessus. Ou bien nous\npouvons référencer des données en JSON et boucler dessus. Je suis sûr qu'on\npourrait mettre en place un système dans lequel des données JSON proviennent\nd’un <a href=\"/2017/12/15/cms-headless/\">CMS headless</a>, d’un processus de\ngénération, d’une fonction serverless, d’une tâche cron ou de ce que vous\nvoulez.</li>\n</ol>\n<p>Maintenant que nous avons juste ce dont nous avons besoin, des cartes répétables\nqui combinent des données et du balisage :</p>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<h3 id=\"creer-autant-de-composants-que-vous-voulez\">Créer autant de composants que vous voulez</h3>\n<p>Vous pouvez partir de ce principe et l’appliquer à l’envi. Imaginez par exemple\nque Bootstrap est essentiellement des bouts des CSS qui suivent des patrons en\nHTML qui définissent leur utilisation. Vous pourriez transformer tous les\npatrons du framework en macro et les appeler en fonction de vos besoins. L'idée\nest celle d’un\n<a href=\"https://css-tricks.com/componentizing-a-framework/\" target=\"_blank\" rel=\"noopener noreferrer\">framework de composants</a>.</p>\n<p>Vous pouvez imbriquer les composants si ça vous chante, et tendre vers quelque\nchose qui ressemble à la philosophie du\n<a href=\"http://bradfrost.com/blog/post/atomic-web-design/\" target=\"_blank\" rel=\"noopener noreferrer\">design atomique</a>.</p>\n<p>Nunjucks offre également une couche de logique, qui fait que vous pouvez créer\ndes composants conditionnels et des variations en passant différents paramètres.</p>\n<p><a href=\"https://thepowerofserverless.info/\" target=\"_blank\" rel=\"noopener noreferrer\">Dans le petit site que j'ai crée</a>, j'ai\ncréé une autre macro pour la section idées du site car les données utilisées et\nle design de la carte sont légèrement différents.</p>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<h3 id=\"un-avis-rapide-sur-les-sites-statiques\">Un avis rapide sur les sites statiques</h3>\n<p>Si la plupart des sites pourraient bénéficier d’une architecture à base de\ncomposants, tous les sites ne sont pas faits pour être statiques. Je travaille\nsur beaucoup de sites pour lesquels utiliser un langage côté serveur est\napproprié et utile.</p>\n<p>Un de mes sites, <a href=\"https://css-tricks.com/\" target=\"_blank\" rel=\"noopener noreferrer\">CSS-Tricks</a>, propose des trucs comme\nun compte utilisateur avec un système de permissions complexe : forums,\ncommentaires, ecommerce. Bien que toutes ces fonctionnalités n'empêchent pas de\ntravailler en statique, je suis souvent bien content d’avoir une base de données\net des langages côté serveur pour travailler. Cela me permet de développer ce\nsont j'ai besoin et de tout avoir au même endroit.</p>\n<h3 id=\"allez-de-l-avant-et-passer-au-statique\">Allez de l’avant et passer au statique</h3>\n<p>Rappelez-vous qu'un des bénéfices de développer le site comme nous l’avons fait\ndans cet article est qu'au final nous obtenons quelques fichiers statiques.\nFaciles à héberger, performants et sécurisés. Pourtant, nous avons pu travailler\nde manière sympa et efficace. Ce site sera simple à mettre à jour par la suite…</p>\n<ul>\n<li>Le projet final est un microsite nommé <em>Le pouvoir du Serverless pour les\ndéveloppeurs Front-End</em> (<a href=\"https://thepowerofserverless.info/\" target=\"_blank\" rel=\"noopener noreferrer\">https://thepowerofserverless.info/</a>).</li>\n<li>L'hébergement de fichier statique fait partie selon moi du mouvement\nserverless.</li>\n<li>Tout le code est visible (et vous pouvez même en faire une copie pour vous)\n<a href=\"https://codepen.io/chriscoyier/project/editor/ZepgLg\" target=\"_blank\" rel=\"noopener noreferrer\">directement dans CodePen</a>.\nIl est maintenu, généré et\n<a href=\"https://blog.codepen.io/projects/custom-domains/\" target=\"_blank\" rel=\"noopener noreferrer\">hébergé</a> entièrement sur\nCodePen à l’aide de <a href=\"https://codepen.io/pro/projects\" target=\"_blank\" rel=\"noopener noreferrer\">CodePen Projects</a>.</li>\n<li>CodePen Projects s'occupe de toute la partie\n<a href=\"https://mozilla.github.io/nunjucks/\" target=\"_blank\" rel=\"noopener noreferrer\">Nunjucks</a> dont nous avons parlé ici,\nainsi que de la compilation Sass et de l’hébergement des images, que j'ai\nutilisé pour le site. Vous pourriez faire la même chose avec par exemple un\nprocess de génération basé sur Gulp voire Grunt par exemple.\n<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>\nque vous pourriez utiliser.</li>\n</ul>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/03/10/mettre-en-place-son-premier-site-sous-hugo/",
      "url": "https://jamstatic.fr/2018/03/10/mettre-en-place-son-premier-site-sous-hugo/",
      "title": "Mettre en place son premier site sous Hugo",
      "summary": "Créer un site statique fonctionnel sous Hugo en moins de 30 minutes",
      "date_published": "2018-03-10T13:57:51+00:00","content_text": "Pour créer un nouveau projet avec Hugo, Forestry propose\nun kit de démarrage en libre téléchargement. Que vous ayez déjà utilisé le\ngénérateur de site statique Hugo ou pas, ce kit est intéressant, car il propose\nune configuration complète et un workflow de développement moderne basé sur les\noutils de l’écosystème de npm.\nChris Macrae nous montre comment s'en servir\npour créer votre premier site en moins de 30 minutes.\nHugo, le générateur de site statique écrit en Go, a pris la\ncommunauté de vitesse. Il présente tous les avantages d’un générateur de site\nstatique — 100% flexible, sécurisé et rapide — mais il vole également la vedette\nquand on\ncompare ses performances avec celles de Jekyll.\nLe site de Forestry.io est d’ailleurs développé avec\nHugo.\nNous allons voir comment configurer Hugo sur votre ordinateur, comment installer\net personnaliser un thème, en ajoutant nos propres fichiers CSS et JavaScript.\nQuelle différence avec le guide de démarrage rapide de la documentation d’Hugo ?\nNous allons utiliser\nnotre kit de démarrage\nrégulièrement mis à jour qui ajoute un workflow de développement moderne à Hugo.\nSommaire\n\n1. Configurer Hugo\n2. Configurer votre site\nMettre à jour un article\nCréer un nouvel article\nUtiliser un thème\n\n\n3. Personnaliser votre site\n4. Personnaliser votre thème\nCSS &amp; Javascript personnalisé\n\n\n5. Prochaine étape\n\n1. Configurer Hugo\nPou commencer, clonez ou\ntéléchargez notre kit de démarrage pour Hugo,\net décompressez l’archive quelque part sur votre ordinateur. Vous avez aussi\nbesoin de Node.js et d’NPM, il\nvous suffit de suivre les indications sur la\npage de téléchargement de Node si vous ne les\navez pas déjà installés.\nVous bénéficiez ainsi automatiquement d’une structure de départ pour Hugo. Dans\nnotre kit, elle est stockée dans le dossier hugo. À l’intérieur se trouvent\ndivers dossiers qui abritent le contenu de votre site, les gabarits de page et\nles fichiers CSS, JS, images, etc. L'arborescence de la structure de base\nressemble à ceci — j'ai laissé quelques fichiers et dossiers de côté de façon à\nce que ce soit plus clair :\n.\n├── hugo\/                  \/\/ Le site Hugo, avec les fichiers de contenu, de données, statiques.\n|   ├── .forestry\/         \/\/ rassemble les fichiers de configuration pour Forestry.io\n|   ├── content\/           \/\/ Tout le contenu du site est stocké ici\n|   ├── data\/              \/\/ Les fichiers de données du site au format TOML, YAML ou JSON\n|   ├── layouts\/           \/\/ Vos modèles de page\n|   |   ├── partials\/      \/\/ Les fichiers partiels réutilisables de votre site\n|   |   ├── shortcodes\/    \/\/ Les fichiers shortcodes de votre site\n|   ├── static\/            \/\/ Les fichiers statiques de votre site\n|   |   ├── css\/           \/\/ Les fichiers CSS compilés\n|   |   ├── img\/           \/\/ Les images du site.\n|   |   ├── js\/            \/\/ Les fichiers JS compilés\n|   |   └── svg\/           \/\/ Les fichiers SVG vont ici\n|   └── config.toml        \/\/ Le fichier de configuration d’Hugo\n└─── src\/\n     ├── css               \/\/ Les fichiers source CSS\/SCSS à compiler vers \/css\/\n     └── js                \/\/ Les fichiers source JS à compiler vers \/js\/\nPour démarrer le projet, ouvrez une fenêtre de terminal et positionnez-vous dans\nle dossier qui contient la structure de départ (hugo-boilerplate par défaut) :\ncd chemin\/vers\/hugo-boilerplate\/\nInstallez ensuite toutes les dépendances du projet en lançant :\nnpm install\nPour lancer le serveur de développement et ouvrir le site dans votre navigateur,\nlancez simplement :\nnpm start\n2. Configurer votre site\nNous allons commencer par ajouter de nouveaux contenus au site. Pour ce faire,\nnous allons devoir mettre à jour le contenu présent dans le dossier\nhugo\/content.\nMettre à jour un article\nCommencer par mettre à jour l’exemple d’article fourni dans notre structure de\ndépart. Ouvrez le fichier hugo\/content\/posts\/example.md dans votre éditeur de\ntexte. Il est composé d’un en-tête front matter avec un champ titre et d’un\ntexte d’exemple au format markdown.\n---\ntitle: \"Bienvenue dans Hugo !\"\n---\n\nVous trouverez la source de cet article dans le répertoire `content\/posts`.\n\nPour ajouter un nouvel article, placez un nouveau fichier dans le dossier\n`content\/posts` en respectant la nomenclature `titre-de-l-article.md` et\najoutez les métadonnées nécessaires dans l’en-tête de page Front Matter.\nJetez un œil au fichier source de cet article pour voir comment ça marche.\n\n&lt;!--more--&gt;\n\nHugo also offers powerful support for code snippets:\n\n    ```go\n    package main\n    import \"fmt\"\n    func print_hi(name string) {\n      fmt.Println(\"Hi, \", name)\n    }\n\n    func main() {\n      print_hi(\"Tom\")\n    }\n    \/\/=&gt; prints 'Hi, Tom' to STDOUT.\n    ```\n\nCheck out the [Hugo docs][hugo-docs] for more info on how to get the most\nout of Hugo. File all bugs\/feature requests at [Hugo’s GitHub\nrepo][hugo-gh]. If you have questions, you can ask them on [Hugo\nCommunity][hugo-community].\n\n[hugo-docs]: https:\/\/gohugo.io\/documentation\/\n[hugo-gh]: https:\/\/github.com\/gohugoio\/hugo\n[hugo-community]: https:\/\/discourse.gohugo.io\/\nCet article n'a pas de date ! Essayez d’en définir une en ajoutant l’entrée\nsuivante dans l’en-tête Front Matter de l’article:\ndate: \"YYYY-MM-DDTHH:MM:SS-00:00\"\nRemplacez YYYY-MM-DDTHH:MM:SS-00:00 avec une date valide, comme…\n2018-01-01T12:42:00-00:00. Si votre date se situe dans le futur, Hugo ne\ngénérera pas cet article en production.\nSauvegardez vos changements puis affichez l’article mis à jour dans votre\nnavigateur à l’adresse http:\/\/localhost:3000\/. La date\naffichée devant le titre de l’article devrait avoir été mise à jour.\nCréer un nouvel article\nMaintenant essayons de créer un nouvel article. Nous utiliserons pour cela la\ncommande fournie avec Hugo pour générer un nouvel article. Dans notre projet,\nHugo est déclaré comme une dépendance NPM, nous pouvons donc l’utiliser avec la\ncommande :\nnpm run hugo -- &lt;command&gt; --&lt;param&gt;\nCréez votre premier article en lançant :\nnpm run hugo -- new posts\/mon-premier-article.md\nCela va créer un nouvel article au format markdown dans\nhugo\/content\/posts\/mon-premier-article.md. Ouvrez ce fichier dans votre\néditeur de texte favori.\n---\ntitle: \"Mon Premier Article\"\ndate: \"2018-03-09T14:24:17-04:00\"\ndraft: true\n---\n\nCe fichier comporte un en-tête Front Matter (des métadonnées structurées\nrelatives à la page) dont on peut tirer parti dans les gabarits de page. Sous le\nfront matter, nous pouvons ajouter du contenu au format markdown :\nAjoutez par exemple le contenu suivant dans le fichier et sauvegardez vos\nchangements :\n## Bienvenue\n\nPratique ce modèle de projet _Hugo_. j'espère que vous appréciez ce guide !\nVous pouvez voir l’article mis à jour dans votre navigateur à l’adresse\nhttp:\/\/localhost:3000\/posts\/mon-premier-article\/.\nUtiliser un thème\nPour le moment votre nouveau site n'est pas très beau. Remédions à cela en\najoutant un thème issu de\nla galerie de thèmes de Hugo, développé par un des\nmeilleurs contributeurs de la communauté.\n\n\n\n\n\n\nLe thème Casper de @vjeantet\n\nNous allons utiliser le thème\nCasper de\n@vjeantet. Pour ce faire nous allons ajouter le\nthème dans le dossier hugo\/themes, plus exactement dans le dossier\nhugo\/themes\/hugo-theme-casper\/.\nClonez ou\ntéléchargez le thème\net décompressez l’archive dans hugo\/themes\/hugo-theme-casper\/.\nEnsuite, mettez à jour la configuration du site aves les options de\nconfiguration spécifiques au thème.\nOuvrez le fichier hugo\/config.toml dans votre éditeur de texte favori et\nremplacer son contenu par celui-ci :\nbaseURL= \"\/\"\nlanguageCode= \"fr\"\ntitle= \"Hugo Boilerplate\"\npaginate = 5\ncopyright = \"Tous droits réservés - 2018\"\ntheme = \"hugo-theme-casper\"\ndisableKinds = [\"taxonomy\", \"taxonomyTerm\"]\n\n[params]\n  description = \"Bien démarrrer avec Hugo\"\n  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é.\"\n  cover = \"\"\n  author = \"VOTRE_NOM\"\n  authorlocation = \"Terre, Galaxie de la Voie Lactée\"\n  authorwebsite = \"\"\n  authorbio= \"\"\n  logo = \"\"\n  hjsStyle = \"default\"\n  paginatedsections = [\"posts\"]\nReportez-vous à la\ndocumentation du thème\npour prendre connaissance de toutes les options de configuration disponibles.\nPour finir, supprimez les exemples de gabarits de page fournis avec notre modèle\nde départ. Pour cela lancez la commande :\nrm -r hugo\/layouts\/\nRegardez maintenant dans votre navigateur et vérifiez que votre site a bien été\nmis à jour !\n3. Personnaliser votre site\nMaintenant que nous avons mis en place un site fonctionnel avec un thème, vous\navez probablement envie de le personnaliser.\nNous allons commencer par éditer les paramètres du site dans le fichier\nhugo\/config.toml. Mettez à jour les valeurs suivantes comme bon vous semble :\n\ntitle = \"Hugo Boilerplate\"\ndescription = \"Bien démarrer avec Hugo\nmetadescription = \"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é\"\nauthor = \"VOTRE_NOM\"\n\n\n\n\n\n\n\nLe thème Casper avec du contenu et les styles par défaut\n\nBien, ajoutons maintenant une image de fond pour la bannière d’en-tête. Dans le\nfichier hugo\/config.toml, vous trouverez une section [params]. Modifiez le\nparamètre cover pour qu'il ait la valeur \/img\/darius-soodmand-116253.jpg,\nsauvegardez vos changements.\n\n\n\n\n\n\nAjout d’une image de fond\n\nRetournons maintenant voir notre site dans le navigateur. C’est déjà mieux, mais\nil y a encore du travail.\n4. Personnaliser votre thème\nMaintenant que vous avez adapté le site pour le personnaliser en peu, nous\nallons nous attarder sur l’aspect le plus puissant d’Hugo et de ce kit de\ndémarrage: un templating simple et puissant.\nNous venons d’ajouter le thème Casper au site, ce qui permet à Hugo d’utiliser\ntous les gabarits HTML présents dans le dossier\nhugo\/themes\/hugo-theme-casper\/layouts\/ lors de la génération du site.\nNous allons maintenant étendre le thème grâce à l’héritage de gabarits\nd’Hugo.\nTous les fichiers de gabarits présents dans le dossier hugo\/layouts\/\nsurchargeront n'importe quel gabarit du même nom présent dans le répertoire des\ngabarits du thème, nous permettant ainsi de personnaliser notre site sans\ntoucher au thème d’origine.\nCSS &amp; Javascript personnalisé\nÀ côté d’Hugo, ce kit de démarrage fourni un serveur de développement qui va\npost-traiter automatiquement les fichiers CSS et JS pour le navigateur. Tous les\nfichiers CSS, JS, images présents dans le dossier src\/ seront traités\nautomatiquement et déplacés dans le dossier hugo\/static\/.\nAjoutons-les à notre thème de manière à pouvoir le personnaliser comme nous\nvoulons. Nous allons copier des fichiers de gabarits du thème et ajouter les\nfichiers CSS et JS personnalisés de notre kit dans ces gabarits.\nPremièrement, nous allons copier le fichier partiel header.html du thème dans\nle dossier hugo\/layouts\/partials\/ :\nmkdir -p hugo\/layouts\/partials\/\ncp hugo\/themes\/hugo-theme-casper\/layouts\/partials\/header.html hugo\/layouts\/partials\/header.html\nOuvrez le fichier hugo\/layouts\/partials\/header.html dans votre éditeur de\ntexte et repérez les lignes suivantes :\n&lt;link rel=\"stylesheet\" type=\"text\/css\" href=\"{{ \"css\/screen.css\" | relURL}}\" \/&gt;\n&lt;link rel=\"stylesheet\" type=\"text\/css\" href=\"{{ \"css\/nav.css\" | relURL}}\" \/&gt;\nAjoutez en dessous la ligne suivante :\n&lt;link rel=\"stylesheet\" type=\"text\/css\" href=\"{{ \"css\/styles.min.css\" | relURL}}\"\n\/&gt;\nEnsuite, recopions le fichier partiel footer.html dans le dossier\nhugo\/layouts\/partials\/ de manière à pouvoir ajouter notre fichier JS\npersonnalisé :\ncp hugo\/themes\/hugo-theme-casper\/layouts\/partials\/footer.html hugo\/layouts\/partials\/footer.html\nOuvrez maintenant le fichier hugo\/layouts\/partials\/footer.html et repérez les\nlignes suivantes :\n&lt;script type=\"text\/javascript\" src=\"{{\"js\/jquery.js\" | relURL}}\"&gt;&lt;\/script&gt;\n&lt;script type=\"text\/javascript\" src=\"{{\"js\/jquery.fitvids.js\" | relURL}}\"&gt;&lt;\/script&gt;\n&lt;script type=\"text\/javascript\" src=\"{{\"js\/index.js\" | relURL}}\"&gt;&lt;\/script&gt;\nAjoutez juste en dessous:\n&lt;script type=\"text\/javascript\" src=\"{{\"js\/scripts.min.js\" | relURL}}\"&gt;&lt;\/script&gt;\nMaintenant tout notre code CSS et JS personnalisé sera utilisé sur le site.\nFaisons un essai en augmentant la hauteur de l’en-tête principal. Ouvrez le\nfichier src\/css\/styles.css et ajoutez le code suivant à la fin du fichier :\n.tag-head.main-header {\n  height: 80vh;\n}\n\n\n\n\n\n\nLe résultat final\n\nAdmirez le résultat final dans votre navigateur !\n5. Prochaine étape\nVous êtes maintenant prêt·e à commencer à créer un site statique avec Hugo !\nVous pouvez continuer à utiliser le thème Casper ou repartir du début en\nutilisant les modèles du répertoire hugo\/layouts\/.\nLes fichiers des modèles de gabarits de page se trouvent dans le\ndépôt de notre kit de démarrage\nsi vous souhaitez repartir de zéro.\nPour en apprendre un peu plus sur Hugo, reportez-vous aux sections suivantes de\nla documentation officielle :\n\nL'organisation des contenus dans Hugo\nIntroduction au langage de templating d’Hugo\nLes options de configuration d’Hugo\n\nNous verrons dans un prochain article comment configurer le versionnement avec\nGit pour faciliter l’intégration continue et le déploiement chez différents\nhébergeurs avec Forestry, un CMS pour les sites statiques générés avec Hugo ou\nJekyll.",
      "content_html": "<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\nun kit de démarrage en libre téléchargement. Que vous ayez déjà utilisé le\ngénérateur de site statique Hugo ou pas, ce kit est intéressant, car il propose\nune configuration complète et un workflow de développement moderne basé sur les\noutils de l’écosystème de <code>npm</code>.\n<a href=\"https://twitter.com/chrisdmacrae\" target=\"_blank\" rel=\"noopener noreferrer\">Chris Macrae</a> nous montre comment s'en servir\npour créer votre premier site en moins de 30 minutes.</p></aside>\n<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\ncommunauté de vitesse. Il présente tous les avantages d’un générateur de site\nstatique — 100% flexible, sécurisé et rapide — mais il vole également la vedette\nquand on\n<a href=\"https://forestry.io/blog/hugo-vs-jekyll-benchmark/\" target=\"_blank\" rel=\"noopener noreferrer\">compare ses performances avec celles de Jekyll</a>.\nLe site de <a href=\"https://forestry.io\" target=\"_blank\" rel=\"noopener noreferrer\">Forestry.io</a> est d’ailleurs développé avec\nHugo.</p>\n<p>Nous allons voir comment configurer Hugo sur votre ordinateur, comment installer\net personnaliser un thème, en ajoutant nos propres fichiers CSS et JavaScript.</p>\n<p>Quelle différence avec le guide de démarrage rapide de la documentation d’Hugo ?\nNous allons utiliser\n<a href=\"https://github.com/forestryio/hugo-boilerplate\" target=\"_blank\" rel=\"noopener noreferrer\">notre kit de démarrage</a>\nrégulièrement mis à jour qui ajoute un workflow de développement moderne à Hugo.</p>\n<p><strong>Sommaire</strong></p>\n<div id=\"toc\"><ul>\n<li><a href=\"#1-configurer-hugo\">1. Configurer Hugo</a></li>\n<li><a href=\"#2-configurer-votre-site\">2. Configurer votre site</a><ul>\n<li><a href=\"#mettre-a-jour-un-article\">Mettre à jour un article</a></li>\n<li><a href=\"#creer-un-nouvel-article\">Créer un nouvel article</a></li>\n<li><a href=\"#utiliser-un-theme\">Utiliser un thème</a></li>\n</ul>\n</li>\n<li><a href=\"#3-personnaliser-votre-site\">3. Personnaliser votre site</a></li>\n<li><a href=\"#4-personnaliser-votre-theme\">4. Personnaliser votre thème</a><ul>\n<li><a href=\"#css-javascript-personnalise\">CSS &amp; Javascript personnalisé</a></li>\n</ul>\n</li>\n<li><a href=\"#5-prochaine-etape\">5. Prochaine étape</a></li>\n</ul></div>\n<h2 id=\"1-configurer-hugo\">1. Configurer Hugo</h2>\n<p>Pou commencer, clonez ou\n<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>,\net décompressez l’archive quelque part sur votre ordinateur. Vous avez aussi\nbesoin 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\nvous suffit de suivre les indications sur la\n<a href=\"https://nodejs.org/fr/download/\" target=\"_blank\" rel=\"noopener noreferrer\">page de téléchargement de Node</a> si vous ne les\navez pas déjà installés.</p>\n<p>Vous bénéficiez ainsi automatiquement d’une structure de départ pour Hugo. Dans\nnotre kit, elle est stockée dans le dossier <code>hugo</code>. À l’intérieur se trouvent\ndivers dossiers qui abritent le contenu de votre site, les gabarits de page et\nles fichiers CSS, JS, images, etc. L'arborescence de la structure de base\nressemble à ceci — j'ai laissé quelques fichiers et dossiers de côté de façon à\nce que ce soit plus clair :</p>\n<pre><code class=\"language-sh hljs bash\">.\n├── hugo/                  // Le site Hugo, avec les fichiers de contenu, de données, statiques.\n|   ├── .forestry/         // rassemble les fichiers de configuration pour Forestry.io\n|   ├── content/           // Tout le contenu du site est stocké ici\n|   ├── data/              // Les fichiers de données du site au format TOML, YAML ou JSON\n|   ├── layouts/           // Vos modèles de page\n|   |   ├── partials/      // Les fichiers partiels réutilisables de votre site\n|   |   ├── shortcodes/    // Les fichiers shortcodes de votre site\n|   ├── static/            // Les fichiers statiques de votre site\n|   |   ├── css/           // Les fichiers CSS compilés\n|   |   ├── img/           // Les images du site.\n|   |   ├── js/            // Les fichiers JS compilés\n|   |   └── svg/           // Les fichiers SVG vont ici\n|   └── config.toml        // Le fichier de configuration d’Hugo\n└─── src/\n     ├── css               // Les fichiers <span class=\"hljs-built_in\">source</span> CSS/SCSS à compiler vers /css/\n     └── js                // Les fichiers <span class=\"hljs-built_in\">source</span> JS à compiler vers /js/</code></pre>\n<p>Pour démarrer le projet, ouvrez une fenêtre de terminal et positionnez-vous dans\nle dossier qui contient la structure de départ (<code>hugo-boilerplate</code> par défaut) :</p>\n<p><code>cd chemin/vers/hugo-boilerplate/</code></p>\n<p>Installez ensuite toutes les dépendances du projet en lançant :</p>\n<p><code>npm install</code></p>\n<p>Pour lancer le serveur de développement et ouvrir le site dans votre navigateur,\nlancez simplement :</p>\n<p><code>npm start</code></p>\n<h2 id=\"2-configurer-votre-site\">2. Configurer votre site</h2>\n<p>Nous allons commencer par ajouter de nouveaux contenus au site. Pour ce faire,\nnous allons devoir mettre à jour le contenu présent dans le dossier\n<code>hugo/content</code>.</p>\n<h3 id=\"mettre-a-jour-un-article\">Mettre à jour un article</h3>\n<p>Commencer par mettre à jour l’exemple d’article fourni dans notre structure de\ndépart. Ouvrez le fichier <code>hugo/content/posts/example.md</code> dans votre éditeur de\ntexte. Il est composé d’un en-tête <em>front matter</em> avec un champ titre et d’un\ntexte d’exemple au format markdown.</p>\n<pre><code class=\"language-markdown hljs markdown\">---\n<span class=\"hljs-section\">title: \"Bienvenue dans Hugo !\"\n---</span>\n\nVous trouverez la source de cet article dans le répertoire <span class=\"hljs-code\">`content/posts`</span>.\n\nPour ajouter un nouvel article, placez un nouveau fichier dans le dossier\n<span class=\"hljs-code\">`content/posts`</span> en respectant la nomenclature <span class=\"hljs-code\">`titre-de-l-article.md`</span> et\najoutez les métadonnées nécessaires dans l’en-tête de page Front Matter.\nJetez un œil au fichier source de cet article pour voir comment ça marche.\n\n<span class=\"xml\"><span class=\"hljs-comment\">&lt;!--more--&gt;</span></span>\n\nHugo also offers powerful support for code snippets:\n\n<span class=\"hljs-code\">    ```go</span>\n<span class=\"hljs-code\">    package main</span>\n<span class=\"hljs-code\">    import \"fmt\"</span>\n<span class=\"hljs-code\">    func print_hi(name string) {</span>\n<span class=\"hljs-code\">      fmt.Println(\"Hi, \", name)</span>\n<span class=\"hljs-code\">    }</span>\n\n<span class=\"hljs-code\">    func main() {</span>\n<span class=\"hljs-code\">      print_hi(\"Tom\")</span>\n<span class=\"hljs-code\">    }</span>\n<span class=\"hljs-code\">    //=&gt; prints 'Hi, Tom' to STDOUT.</span>\n<span class=\"hljs-code\">    ```</span>\n\nCheck 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\nout of Hugo. File all bugs/feature requests at [Hugo’s GitHub\nrepo][hugo-gh]. If you have questions, you can ask them on [Hugo\nCommunity][hugo-community].\n\n[<span class=\"hljs-symbol\">hugo-docs</span>]: <span class=\"hljs-link\">https://gohugo.io/documentation/</span>\n[<span class=\"hljs-symbol\">hugo-gh</span>]: <span class=\"hljs-link\">https://github.com/gohugoio/hugo</span>\n[<span class=\"hljs-symbol\">hugo-community</span>]: <span class=\"hljs-link\">https://discourse.gohugo.io/</span></code></pre>\n<p>Cet article n'a pas de date ! Essayez d’en définir une en ajoutant l’entrée\nsuivante dans l’en-tête <em>Front Matter</em> de l’article:</p>\n<p><code>date: \"YYYY-MM-DDTHH:MM:SS-00:00\"</code></p>\n<aside class=\"note note-tip\"><p><em>Remplacez</em> <code>YYYY-MM-DDTHH:MM:SS-00:00</code> avec une date valide, comme…\n<code>2018-01-01T12:42:00-00:00</code>. Si votre date se situe dans le futur, Hugo ne\ngénérera pas cet article en production.</p></aside>\n<p>Sauvegardez vos changements puis affichez l’article mis à jour dans votre\nnavigateur à l’adresse <a href=\"http://localhost:3000/\" target=\"_blank\" rel=\"noopener noreferrer\">http://localhost:3000/</a>. La date\naffichée devant le titre de l’article devrait avoir été mise à jour.</p>\n<h3 id=\"creer-un-nouvel-article\">Créer un nouvel article</h3>\n<p>Maintenant essayons de créer un nouvel article. Nous utiliserons pour cela la\ncommande fournie avec Hugo pour générer un nouvel article. Dans notre projet,\nHugo est déclaré comme une dépendance NPM, nous pouvons donc l’utiliser avec la\ncommande :</p>\n<p><code>npm run hugo -- &lt;command&gt; --&lt;param&gt;</code></p>\n<p>Créez votre premier article en lançant :</p>\n<p><code>npm run hugo -- new posts/mon-premier-article.md</code></p>\n<p>Cela va créer un nouvel article au format markdown dans\n<code>hugo/content/posts/mon-premier-article.md</code>. Ouvrez ce fichier dans votre\néditeur de texte favori.</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-meta\">---</span>\n<span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">\"Mon Premier Article\"</span>\n<span class=\"hljs-attr\">date:</span> <span class=\"hljs-string\">\"2018-03-09T14:24:17-04:00\"</span>\n<span class=\"hljs-attr\">draft:</span> <span class=\"hljs-literal\">true</span>\n<span class=\"hljs-meta\">---</span>\n</code></pre>\n<p>Ce fichier comporte un en-tête Front Matter (des métadonnées structurées\nrelatives à la page) dont on peut tirer parti dans les gabarits de page. Sous le\n<em>front matter</em>, nous pouvons ajouter du contenu au format markdown :</p>\n<p>Ajoutez par exemple le contenu suivant dans le fichier et sauvegardez vos\nchangements :</p>\n<pre><code class=\"language-md hljs markdown\"><span class=\"hljs-section\">## Bienvenue</span>\n\nPratique ce modèle de projet <span class=\"hljs-emphasis\">_Hugo_</span>. j'espère que vous appréciez ce guide !</code></pre>\n<p>Vous pouvez voir l’article mis à jour dans votre navigateur à l’adresse\n<a href=\"http://localhost:3000/posts/mon-premier-article/\" target=\"_blank\" rel=\"noopener noreferrer\">http://localhost:3000/posts/mon-premier-article/</a>.</p>\n<h3 id=\"utiliser-un-theme\">Utiliser un thème</h3>\n<p>Pour le moment votre nouveau site n'est pas très beau. Remédions à cela en\najoutant un thème issu de\n<a href=\"https://themes.gohugo.io/\" target=\"_blank\" rel=\"noopener noreferrer\">la galerie de thèmes de Hugo</a>, développé par un des\nmeilleurs contributeurs de la communauté.</p>\n<figure>\n<picture title=\"Le thème Casper de @vjeantet\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Le thème Casper de @vjeantet</figcaption>\n</figure>\n<p>Nous allons utiliser le thème\n<a href=\"https://github.com/vjeantet/hugo-theme-casper\" target=\"_blank\" rel=\"noopener noreferrer\">Casper</a> de\n<a href=\"https://github.com/vjeantet\" target=\"_blank\" rel=\"noopener noreferrer\"><em>@vjeantet</em></a>. Pour ce faire nous allons ajouter le\nthème dans le dossier <code>hugo/themes</code>, plus exactement dans le dossier\n<code>hugo/themes/hugo-theme-casper/</code>.</p>\n<p>Clonez ou\n<a href=\"https://github.com/vjeantet/hugo-theme-casper/archive/master.zip\" target=\"_blank\" rel=\"noopener noreferrer\">téléchargez le thème</a>\net décompressez l’archive dans <code>hugo/themes/hugo-theme-casper/</code>.</p>\n<p>Ensuite, mettez à jour la configuration du site aves les options de\nconfiguration spécifiques au thème.</p>\n<p>Ouvrez le fichier <code>hugo/config.toml</code> dans votre éditeur de texte favori et\nremplacer son contenu par celui-ci :</p>\n<pre><code class=\"language-toml hljs ini\"><span class=\"hljs-attr\">baseURL</span>= <span class=\"hljs-string\">\"/\"</span>\n<span class=\"hljs-attr\">languageCode</span>= <span class=\"hljs-string\">\"fr\"</span>\n<span class=\"hljs-attr\">title</span>= <span class=\"hljs-string\">\"Hugo Boilerplate\"</span>\n<span class=\"hljs-attr\">paginate</span> = <span class=\"hljs-number\">5</span>\n<span class=\"hljs-attr\">copyright</span> = <span class=\"hljs-string\">\"Tous droits réservés - 2018\"</span>\n<span class=\"hljs-attr\">theme</span> = <span class=\"hljs-string\">\"hugo-theme-casper\"</span>\n<span class=\"hljs-attr\">disableKinds</span> = [<span class=\"hljs-string\">\"taxonomy\"</span>, <span class=\"hljs-string\">\"taxonomyTerm\"</span>]\n\n<span class=\"hljs-section\">[params]</span>\n  description = \"Bien démarrrer avec Hugo\"\n  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é.\"\n  cover = \"\"\n  author = \"VOTRE_NOM\"\n  authorlocation = \"Terre, Galaxie de la Voie Lactée\"\n  authorwebsite = \"\"\n  authorbio= \"\"\n  logo = \"\"\n  hjsStyle = \"default\"\n  paginatedsections = <span class=\"hljs-section\">[\"posts\"]</span></code></pre>\n<p>Reportez-vous à la\n<a href=\"https://github.com/vjeantet/hugo-theme-casper#configuration\" target=\"_blank\" rel=\"noopener noreferrer\">documentation du thème</a>\npour prendre connaissance de toutes les options de configuration disponibles.</p>\n<p>Pour finir, supprimez les exemples de gabarits de page fournis avec notre modèle\nde départ. Pour cela lancez la commande :</p>\n<pre><code class=\"language-sh hljs bash\">rm -r hugo/layouts/</code></pre>\n<p>Regardez maintenant dans votre navigateur et vérifiez que votre site a bien été\nmis à jour !</p>\n<h2 id=\"3-personnaliser-votre-site\">3. Personnaliser votre site</h2>\n<p>Maintenant que nous avons mis en place un site fonctionnel avec un thème, vous\navez probablement envie de le personnaliser.</p>\n<p>Nous allons commencer par éditer les paramètres du site dans le fichier\n<code>hugo/config.toml</code>. Mettez à jour les valeurs suivantes comme bon vous semble :</p>\n<ul>\n<li><code>title = \"Hugo Boilerplate\"</code></li>\n<li><code>description = \"Bien démarrer avec Hugo</code></li>\n<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>\n<li><code>author = \"VOTRE_NOM\"</code></li>\n</ul>\n<figure>\n<picture title=\"Le thème Casper avec du contenu et les styles par défaut\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Le thème Casper avec du contenu et les styles par défaut</figcaption>\n</figure>\n<p>Bien, ajoutons maintenant une image de fond pour la bannière d’en-tête. Dans le\nfichier <code>hugo/config.toml</code>, vous trouverez une section <code>[params]</code>. Modifiez le\nparamètre <code>cover</code> pour qu'il ait la valeur <code>/img/darius-soodmand-116253.jpg</code>,\nsauvegardez vos changements.</p>\n<figure>\n<picture title=\"Ajout d’une image de fond\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Ajout d’une image de fond</figcaption>\n</figure>\n<p>Retournons maintenant voir notre site dans le navigateur. C’est déjà mieux, mais\nil y a encore du travail.</p>\n<h2 id=\"4-personnaliser-votre-theme\">4. Personnaliser votre thème</h2>\n<p>Maintenant que vous avez adapté le site pour le personnaliser en peu, nous\nallons nous attarder sur l’aspect le plus puissant d’Hugo et de ce kit de\ndémarrage: <strong>un templating simple et puissant</strong>.</p>\n<p>Nous venons d’ajouter le thème Casper au site, ce qui permet à Hugo d’utiliser\ntous les gabarits HTML présents dans le dossier\n<code>hugo/themes/hugo-theme-casper/layouts/</code> lors de la génération du site.</p>\n<p>Nous allons maintenant <em>étendre</em> le thème grâce à <strong>l’héritage de gabarits</strong>\nd’Hugo.</p>\n<p>Tous les fichiers de gabarits présents dans le dossier <code>hugo/layouts/</code>\nsurchargeront n'importe quel gabarit du même nom présent dans le répertoire des\ngabarits du thème, nous permettant ainsi de personnaliser notre site sans\ntoucher au thème d’origine.</p>\n<h3 id=\"css-javascript-personnalise\">CSS &amp; Javascript personnalisé</h3>\n<p>À côté d’<em>Hugo</em>, ce kit de démarrage fourni un serveur de développement qui va\npost-traiter automatiquement les fichiers CSS et JS pour le navigateur. Tous les\nfichiers CSS, JS, images présents dans le dossier <code>src/</code> seront traités\nautomatiquement et déplacés dans le dossier <code>hugo/static/</code>.</p>\n<p>Ajoutons-les à notre thème de manière à pouvoir le personnaliser comme nous\nvoulons. Nous allons copier des fichiers de gabarits du thème et ajouter les\nfichiers CSS et JS personnalisés de notre kit dans ces gabarits.</p>\n<p>Premièrement, nous allons copier le fichier partiel <em>header.html</em> du thème dans\nle dossier <code>hugo/layouts/partials/</code> :</p>\n<pre><code class=\"language-sh hljs bash\">mkdir -p hugo/layouts/partials/\ncp hugo/themes/hugo-theme-casper/layouts/partials/header.html hugo/layouts/partials/header.html</code></pre>\n<p>Ouvrez le fichier <code>hugo/layouts/partials/header.html</code> dans votre éditeur de\ntexte et repérez les lignes suivantes :</p>\n<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>\n<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>\n<p>Ajoutez en dessous la ligne suivante :</p>\n<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>}}\"\n/&gt;</span></code></pre>\n<p>Ensuite, recopions le fichier partiel <code>footer.html</code> dans le dossier\n<code>hugo/layouts/partials/</code> de manière à pouvoir ajouter notre fichier JS\npersonnalisé :</p>\n<pre><code class=\"language-sh hljs bash\">cp hugo/themes/hugo-theme-casper/layouts/partials/footer.html hugo/layouts/partials/footer.html</code></pre>\n<p>Ouvrez maintenant le fichier <code>hugo/layouts/partials/footer.html</code> et repérez les\nlignes suivantes :</p>\n<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>\n<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>\n<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>\n<p>Ajoutez juste en dessous:</p>\n<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>\n<p>Maintenant tout notre code CSS et JS personnalisé sera utilisé sur le site.</p>\n<p>Faisons un essai en augmentant la hauteur de l’en-tête principal. Ouvrez le\nfichier <code>src/css/styles.css</code> et ajoutez le code suivant à la fin du fichier :</p>\n<pre><code class=\"language-css hljs css\"><span class=\"hljs-selector-class\">.tag-head</span><span class=\"hljs-selector-class\">.main-header</span> {\n  <span class=\"hljs-attribute\">height</span>: <span class=\"hljs-number\">80vh</span>;\n}</code></pre>\n<figure>\n<picture title=\"Le résultat final\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Le résultat final</figcaption>\n</figure>\n<p>Admirez le résultat final dans votre navigateur !</p>\n<h2 id=\"5-prochaine-etape\">5. Prochaine étape</h2>\n<p>Vous êtes maintenant prêt·e à commencer à créer un site statique avec Hugo !</p>\n<p>Vous pouvez continuer à utiliser le thème Casper ou repartir du début en\nutilisant les modèles du répertoire <code>hugo/layouts/</code>.</p>\n<aside class=\"note note-tip\"><p>Les fichiers des modèles de gabarits de page se trouvent dans le\n<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>\nsi vous souhaitez repartir de zéro.</p></aside>\n<p>Pour en apprendre un peu plus sur Hugo, reportez-vous aux sections suivantes de\nla documentation officielle :</p>\n<ul>\n<li><a href=\"https://gohugo.io/content-management/organization/\" target=\"_blank\" rel=\"noopener noreferrer\">L'organisation des contenus dans Hugo</a></li>\n<li><a href=\"https://gohugo.io/templates/introduction/\" target=\"_blank\" rel=\"noopener noreferrer\">Introduction au langage de templating d’Hugo</a></li>\n<li><a href=\"https://gohugo.io/getting-started/configuration/\" target=\"_blank\" rel=\"noopener noreferrer\">Les options de configuration d’Hugo</a></li>\n</ul>\n<p>Nous verrons dans un prochain article comment configurer le versionnement avec\nGit pour faciliter l’intégration continue et le déploiement chez différents\nhébergeurs avec Forestry, un CMS pour les sites statiques générés avec Hugo ou\nJekyll.</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/02/18/presentation-eleventy/",
      "url": "https://jamstatic.fr/2018/02/18/presentation-eleventy/",
      "title": "Présentation d’Eleventy, un nouveau générateur de site statique",
      "summary": "Zach Leatherman explique pourquoi il a développé Eleventy, un générateur de site statique basé sur JavaScript, particulièrement flexible et qui sait se faire oublier.",
      "date_published": "2018-02-18T09:23:07+00:00","content_text": "L'auteur d’Eleventy, le talentueux Zach Leatherman, 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.\nEleventy est un nouveau générateur de site statique.\nSi n'êtes pas encore au fait de ce que sont les générateurs de sites statiques et des avantages qu'ils procurent, lisez ce très bon article paru dans Smashing Magazine écrit par Matt Biilmann.\nEncore un générateur de site statique ? Oui. Mais pourquoi ? Bonne question.\nEleventy a été créé pour trois raisons :\n\nFlexibilité\nParier sur JavaScript\nCe n'est pas un framework JavaScript\n\nFlexibilité\nFlexibilité du moteur de template\nEleventy 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 !\nLinda 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.\n\n\n\nGénérateur de site\nClassement staticgen.com\nMoteur de templates\n\n\n\n\nJekyll\n#1\nLiquid\n\n\nHugo\n#2\nGo Template\n\n\nHexo\n#3\nEJS, Pug\n\n\nGatsby\n#4\nReact.js\n\n\n\nEleventy supporte actuellement :\n\nHTML\nMarkdown\nLiquid (used by Jekyll)\nNunjucks\nHandlebars\nMustache\nEJS\nHaml\nPug\nJavaScript Template Literals (ES2015)\n\nFlexibilité de l’arborescence de répertoires\nEleventy 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 source, content ou _posts (sauf si vous le désirez). Vous dites à Eleventy où sont vos fichiers et il se débrouillera avec.\nUn simple eleventy en ligne de commande va traiter les fichiers présents dans le répertoire courant et générer un site dans un dossier _site. Vous pouvez préciser votre choix à l’aide des options --input et --output.\nTraite les fichiers du répertoire courant et génère un dossier _site\neleventy\nTraite les fichiers du dossier src et génère un dossier _gh_pages\neleventy --input=src --output=_gh_pages\nTraite les fichiers du répertoire courant et génère les fichiers au même endroit\neleventy --input=. --output=.\nTransformer un fichier à la fois\nEleventy fonctionne aussi comme un petit utilitaire permettant de traiter un seul fichier. Pour transformer le fichier README.md en README.html.\neleventy --input=README.md --output=.\nPariez sur JavaScript\nPariez toujours sur JavaScript. JavaScript vous donne accès à npm. L'écosystème d’npm est immense. Follement immense. Et il continue de gagner en popularité. Selon modulecounts.com, 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.\n\n\n\nGénérateur de site\nLangage\nNombre de modules\n\n\n\n\nJekyll\nRuby\n~140,000 sur rubygems.org\n\n\nHugo\nGo\n~20,000 sur Gopm\n\n\nHexo\nJavaScript\n~580,000 sur npm\n\n\nGatsby\nJavaScript\n~580,000 sur npm\n\n\n\nEleventy n'est pas un framework JavaScript\nBien 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.\nIl 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.\nLa 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.\nCes bibliothèques peuvent être lourdes et parfois bloquer le rendu du contenu critique ou encore causer des congestions au niveau réseau dans le rendu de contenu critique avec preload.\nLa 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.\nTestez Eleventy !\nJ'espère que vous donnerez sa chance à Eleventy ! Installez-le !\nnpm install -g @11ty\/eleventy\nVous trouverez des tutoriels sur 11ty.dev. Dites à Zach ce que vous aimez ou que vous n'aimez pas ! il adorerait avoir vos retours.\nUne chose sympa et facile à faire que vous pouvez faire pour soutenir le projet est de le marquer d’une étoile sur GitHub. Comme la liste gigantesque des générateurs de site statique de staticgen.com est classée en fonction du nombre d’étoiles sur GitHub, ça aiderait le projet à gagner pas mal de places au classement. Merci !",
      "content_html": "<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>\n<p>Eleventy est un nouveau générateur de site statique.</p>\n<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>\n<p>Encore un générateur de site statique ? Oui. Mais pourquoi ? Bonne question.</p>\n<p>Eleventy a été créé pour trois raisons :</p>\n<ul>\n<li>Flexibilité</li>\n<li>Parier sur JavaScript</li>\n<li>Ce n'est pas un framework JavaScript</li>\n</ul>\n<h2 id=\"flexibilite\">Flexibilité</h2>\n<h3 id=\"flexibilite-du-moteur-de-template\">Flexibilité du moteur de template</h3>\n<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>\n<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>\n<table>\n<thead>\n<tr>\n<th>Générateur de site</th>\n<th>Classement <a href=\"https://www.staticgen.com/\" target=\"_blank\" rel=\"noopener noreferrer\">staticgen.com</a></th>\n<th>Moteur de templates</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Jekyll</td>\n<td>#1</td>\n<td>Liquid</td>\n</tr>\n<tr>\n<td>Hugo</td>\n<td>#2</td>\n<td>Go Template</td>\n</tr>\n<tr>\n<td>Hexo</td>\n<td>#3</td>\n<td>EJS, Pug</td>\n</tr>\n<tr>\n<td>Gatsby</td>\n<td>#4</td>\n<td>React.js</td>\n</tr>\n</tbody>\n</table>\n<p>Eleventy supporte actuellement :</p>\n<ul>\n<li>HTML</li>\n<li><a href=\"https://github.com/markdown-it/markdown-it\" target=\"_blank\" rel=\"noopener noreferrer\">Markdown</a></li>\n<li><a href=\"https://www.npmjs.com/package/liquidjs\" target=\"_blank\" rel=\"noopener noreferrer\">Liquid</a> (used by Jekyll)</li>\n<li><a href=\"https://mozilla.github.io/nunjucks/\" target=\"_blank\" rel=\"noopener noreferrer\">Nunjucks</a></li>\n<li><a href=\"https://github.com/wycats/handlebars.js\" target=\"_blank\" rel=\"noopener noreferrer\">Handlebars</a></li>\n<li><a href=\"https://github.com/janl/mustache.js/\" target=\"_blank\" rel=\"noopener noreferrer\">Mustache</a></li>\n<li><a href=\"https://www.npmjs.com/package/ejs\" target=\"_blank\" rel=\"noopener noreferrer\">EJS</a></li>\n<li><a href=\"https://github.com/tj/haml.js\" target=\"_blank\" rel=\"noopener noreferrer\">Haml</a></li>\n<li><a href=\"https://github.com/pugjs/pug\" target=\"_blank\" rel=\"noopener noreferrer\">Pug</a></li>\n<li>JavaScript Template Literals (ES2015)</li>\n</ul>\n<h3 id=\"flexibilite-de-l-arborescence-de-repertoires\">Flexibilité de l’arborescence de répertoires</h3>\n<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>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">eleventy</code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">eleventy --input=src --output=_gh_pages</code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">eleventy --input=. --output=.</code></pre>\n<h4 id=\"transformer-un-fichier-a-la-fois\">Transformer un fichier à la fois</h4>\n<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>\n<pre><code class=\"language-sh hljs bash\">eleventy --input=README.md --output=.</code></pre>\n<h2 id=\"pariez-sur-javascript\">Pariez sur JavaScript</h2>\n<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>\n<table>\n<thead>\n<tr>\n<th>Générateur de site</th>\n<th>Langage</th>\n<th>Nombre de modules</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Jekyll</td>\n<td>Ruby</td>\n<td>~140,000 sur rubygems.org</td>\n</tr>\n<tr>\n<td>Hugo</td>\n<td>Go</td>\n<td>~20,000 sur Gopm</td>\n</tr>\n<tr>\n<td>Hexo</td>\n<td>JavaScript</td>\n<td>~580,000 sur npm</td>\n</tr>\n<tr>\n<td>Gatsby</td>\n<td>JavaScript</td>\n<td>~580,000 sur npm</td>\n</tr>\n</tbody>\n</table>\n<h2 id=\"eleventy-n-est-pas-un-framework-javascript\">Eleventy n'est pas un framework JavaScript</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"testez-eleventy\">Testez Eleventy !</h2>\n<p>J'espère que vous donnerez sa chance à Eleventy ! Installez-le !</p>\n<pre><code class=\"language-sh hljs bash\">npm install -g @11ty/eleventy</code></pre>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/02/09/scratch-hugo/",
      "url": "https://jamstatic.fr/2018/02/09/scratch-hugo/",
      "title": "La fonction .Scratch d’Hugo",
      "summary": "Pendant longtemps, la fonction Scratch a été le seul moyen d'écraser des variables en Go Templating. Elle reste toujours le meilleur moyen d'enrichir votre contexte de page ou de shortcode dans Hugo.",
      "date_published": "2018-02-09T19:50:50+00:00",
      "date_modified": "2018-08-29T15:09:47+00:00","content_text": "Vous êtes ici pour apprendre à écraser une variable dans un gabarit de page ? Bonne nouvelle, vous n'avez plus besoin de la fonction .Scratch pour cela depuis la version 0.48 d'Hugo. Malgré cela, .Scratch reste encore utile pour plein d'autres choses !\nLe 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.\nAvec la fonction .Scratch d'Hugo, n'importe quelle Page ou Shortcode peut être enrichie avec autant de variables que nécessaire en plus de celles par défaut.\nC'est quoi Scratch ?\nScratch a été ajouté à l'origine pour contourner une limitation 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.\nÀ des fins de lisibilité, les extraits de code qui suivent ont des commentaires incompatibles avec le langage de template de Go. Reportez vous à la doc pour comment commenter dans Hugo.\n.Scratch.Set\nSet est utilisé pour mémoriser une valeur voire pour pouvoir surcharger simplement une valeur par la suite.\n{{ .Scratch.Set \"salutations\" \"Bonjour\" }}\n{{ if eq $ciel \"sombre\" }}\n    {{ .Scratch.Set \"salutations\" \"Bonsoir\" }}\n{{ end }}\n\n{{ .Scratch.Get \"salutations\"}}\n.Scratch.Add\nCette méthode s'occupe d’ajouter ou de pousser des valeurs multiples dans une\nvariable ou une clef.\n\/\/ Pour les chaînes de caractères\n{{ .Scratch.Add \"salutations\" \"Bonjour\" }}\n{{ .Scratch.Add \"salutations\" \"Bonsoir\" }}\n\n{{ .Scratch.Get \"salutations\" }}\n\/\/ Affichera : BonjourBonsoir\nUtilisée avec slice, elle permet d’ajouter une ou plusieurs valeurs à un\ntableau.\n{{ .Scratch.Add \"salutations\" (slice \"Bonjour\") }}\n{{ .Scratch.Add \"salutations\" (slice \"Bonsoir\") }}\n{{ .Scratch.Add \"salutations\" (slice \"Aloha\" \"Buenos dias\") }}\n.Scratch.Get\nMaintenant récupérons tout ça.\n\/\/ Avec la fonction range\n{{ range where .Scratch.Get \"salutations\" }}\n&lt;ol&gt;\n    &lt;li&gt;\n        {{ . }}\n    &lt;\/li&gt;\n&lt;\/ol&gt;\n{{ end }}\n\/\/ ☝️ Affichera une liste ordonnée avec nos 4 salutations.\n\n\/\/ Ou avec la fonction delimit\n{{ delimit (.Scratch.Get \"salutations\"), \", \" }}\n\/\/ ☝️ Affichera Bonjour, Bonsoir, Aloha, Buenos dias\n.Scratch.Delete1\nSupprime la paire clé\/valeur du contexte.\nLors de l'utilisation de .Scratch.Add dans une boucle, .Scratch.Delete est pratique pour réinitialiser une valeur.\n{{ .Scratch.Delete \"salutations\" }}\nnewScratch2\nCe 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.\n{{ $headerScratch := newScratch }}\n{{ $headerScratch.Add \"brand_image\" .Params.image }}\nManipuler des tableaux et des maps\n.Scratch.SetInMap\nCette fonction-là permet de cibler la clef d’un tableau et de lui assigner une\nnouvelle valeur.\nElle prend comme premier paramètre votre clef .Scratch, comme second paramètre\nla clef issue du tableau ou de la map, le troisième étant la valeur que vous\ndéfinissez.\nSi vous ne connaissez pas dict je\nvous explique tout ça\ndans cet article\n{{ .Scratch.Add \"salutations\" (dict \"english\" \"Hello\" \"french\" \"Bonjour\") }}\n\n{{ .Scratch.SetInMap \"salutations\" \"english\" \"Howdy 🤠\" }}\n\n\/\/ Nous avons modifié la valeur de la clef en anglais de Hello à Howdy 🤠 !\nAttention au périmètre et au contexte…\n.Scratch n'est disponible que pour l’objet page ou l’objet shortcode. Vous ne\npouvez pas l’utiliser sur un autre élément.\nSouvenez-vous que si vous vous trouvez à l’intérieur d’une boucle range dans\nvotre page d’index, alors le .Scratch de votre page d’index sera $.Scratch\nalors que la page courante que vous traitez dans votre boucle sera .Scratch.\nRetenez également que vous pouvez affecter une paire clef-valeur à .Scratch\ndepuis n'importe où, même dans un fichier partiel du moment que vous lui passez\nle contexte. Heeeeein? Prenons un exemple concret pour illustrer les dangers qui\nvous guettent avec l’utilisation de .Scratch et du contexte.\nUn exemple classe avec .Scratch\nJe trouve ça bien pratique d’affecter des classes à mon élément body (comme le\nfait WordPress) pour pouvoir faire des ajustements CSS\/JavaScript en fonction\nde la page sur laquelle on se trouve.\nJe trouvais ça très fastidieux à faire avec Hugo, jusqu'à ce que je comprenne\ncomment utiliser .Scratch.\nJe veux ajouter une classe CSS rp-body à toutes mes pages ainsi que la valeur\nde .Section à mes classes.\nEt seule la page d’accueil devrait hériter de la classe rp-home.\nJe pourrais écrire ça une bonne foi pour toute, dans un fichier partiel ou un\nfichier de gabarit de page qui comprend l’ouverture de la balise body mais… je\npourrais avoir besoin de cette liste de classes ailleurs dans mon code pour\nréaliser des tours de magie avec ajax. Disons sous forme d’objet JavaScript.\nComme faire pour créer cette liste, la modifier si je suis sur la page d’accueil\net la stocker dans mon objet .Page pour pouvoir la réutiliser par la suite ?\nPour bien faire, nous allons stocker nos classes dans un tableau.\n\/\/ Avant la balise body, je peux stocker mon unique et première classe universelle.\n{{ .Scratch.Add \"classes\" (slice \"rp-body\") }}\n\n\/\/ Puis ma section. Ce printf me permet d’ajouter la valeur de .Section avec mon préfixe personnalisé.\n{{ .Scratch.Add \"classes\" (slice (printf \"rp-%s\" .Section))) }}\n\n\/\/ Et maintenant sommes nous sur la page d’accueil ?\n{{ if .IsHome }}\n    {{ .Scratch.Add \"classes\" (slice \"rp-home\") }}\n{{end}}\n\/\/ Est-ce que ce sont les vacances ? 🎄\n{{ if isset .Site.Params \"season\" }}\n    {{ .Scratch.Add \"classes\" (slice (printf \"rp-body--%s\" .Site.Params.season))) }}\n{{ end }}\nNous pourrions faire bien plus de vérifications et de contorsions, mais en fin\nde compte, nous n'avons plus qu'à écrire dans notre fichier de gabarit ce joli :\n&lt;body class='{{ delimit (.Scratch.Get \"classes\") \" \" }}'&gt;\nEt pour JavaScript, nous pouvons créer notre objet à l’endroit où nous en avons\nbesoin.\n&lt;script&gt;\n    let bodyClasses = [{{ range .Scratch.Get \"classes\" }}\"{{ . }}\", {{end}}];\n&lt;\/script&gt;\nTrès bon cas de figure, continuons notre chemin.\n.Scratch dans un fichier partiel\nComme je l’expliquais plus tôt, comme .Scratch fait partie de l’objet page\ngénéralement passé en tant que contexte (le fameux point) à l’appel de la fonction\npartial.Déplaçons le bout de code qui stocke nos classes dans un fichier\npartiel pour gagner en lisibilité :\n\/\/ partials\/scratching\/body_classes.html\n{{ .Scratch.Add \"classes\" (slice \"rp-body\") }}\n[… ici le code vu précédemment  …]\nDans mon fichier de gabarit, je peux maintenant écrire :\n{{ partial \"scratching\/body_classes.html\" . }}\n&lt;body class='{{ delimit (.Scratch.Get \"classes\") \" \" }}'&gt;\n[…]\nLe retour de la fonction .Scratch de la page a été transmis au fichier partiel\nvia le contexte, de manière à pouvoir continuer de le modifier sans avoir à\ntoucher au code ailleurs. En plus ça permet d’avoir des fichiers de gabarits de\npage plus propres !\n.Scratch dans un fichier partiel dans une boucle range 🤯\nQuand vous utilisez la fonction range pour boucler sur des éléments, vous ne\npouvez pas lui passer le contexte en paramètre comme on peut le faire avec la\nfonction partial, le contexte que vous manipulez est celui de la boucle, c'est\nbien ce que vous souhaitez.\n{{ .Scratch.Set \"section_color\" }}\n{{ range where .Data.Pages}}\n   &lt;h2&gt;{{ .Title }}&lt;\/h2&gt;\n     &lt;div class=\"Child Child--{{ $.Scratch.Get section_color}}\"&gt;\n     […]\n     &lt;div&gt;\n{{ end }}\n\/\/ Affichera le contenu de section_color.\n\n\/\/ Alors que…\n{{ range where .Data.Pages }}\n      {{ partial \"enfant.html\" . }}\n{{ end }}\n\n\/\/ 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…\nC’est parce que le contexte que nous passons en paramètre de la fonction\npartial est celui de l’élément en cours parcouru grâce à la fonction range,\npas celui de la page dont vous êtes en train de coder le gabarit.\nTrès bien me direz-vous, mais comment faire pour accéder au .Scratch de la\npage parente depuis notre fichier partiel ?\nEh bien, vous pouvez toujours stocker ce qui est retourné par la fonction\n.Scratch de la page dans une variable, pour la passer ensuite en paramètre de\nvotre fichier partiel :\n{{ $indexScratch := .Scratch }}\n  {{ range where .Data.Pages }}\n      {{ partial \"child.html\" $indexScratch }}\n  {{ end }}\nDans le fichier partiel on écrira alors :\n&lt;div class=\"Child Child--{{ .Get \"section_color\" }}\"&gt;\n[…]\n&lt;div&gt;\nSi vous avez également besoin de l’ensemble du contexte de la page que vous êtes\nen train de parcourir dans la boucle, utilisez alors la fonction dict :\n{{ $indexScratch := .Scratch }}\n  {{ range where .Data.Pages }}\n      {{ partial \"child.html\" (dict \"indexScratch\" $indexScratch \"page\" . }}\n  {{ end }}\nDans le fichier partiel vous pourrez alors écrire :\n&lt;div class=\"Child Child--{{ .indexScratch.Get section_color}}\"&gt;\n    {{ .page.Content }}\n&lt;div&gt;\n.Scratch dans un fichier partiel sans contexte de page\nTout 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 newScratch2, vous pouvez utiliser désormais utiliser Scratch n'importe où, y compris dans un fichier partiel sans contexte de Page.\nAppelons un fichier partiel. Notez que nous ne passons aucun contexte de Page, juste une map issue du Front Matter qui contient class, alt et une potentielle image_src pour remplacer celle par défaut.\n{{ partial \"brand\" .Params.brand }}\nDans notre fichier partiel nous pouvons toujours faire appel à Scratch :\n{{ $brandScratch := newScratch }}\n{{ $brandScratch.Set \"brand_image\" \"default.jpg\" }}\n{{ with .image_src }}\n  {{ $brandScratch.Set \"brand_image\" \".\" }}\n{{ end }}\n&lt;div class=\"brand {{ .class }}\"&gt;\n  &lt;img src=\"{{ $brandScratch.Get \"brand_image\" }}\" alt=\"{{ .alt }}\" \/&gt;\n&lt;\/div&gt;\n.Scratch après Go 1.11\nOui, avec la version 11 de Golang nous pouvons maintenant nativement écraser les variables dans les templates Go mais …\nDans 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 dict…\n{{ $humeur := \"Joyeux\" }}\n{{ if $pluie }}\n    {{ $humeur = \"Grincheux\" }}\n{{ end }}\n{{ partial \"blancheneige\/nain.html\" (dict \"humeur\" $humeur \"page\" . ) }}\nUtiliser Scratch pour stocker vos variables dans l'objet de Page vous garantit un code propre et réutilisable.\nAvec .Scratch\n{{ .Scratch.Set \"humeur\" \"Joyeux\" }}\n{{ if $pluie }}\n    {{ .Scratch.Set \"humeur\" \"Grincheux\" }}\n{{ end }}\n{{ partial \"blancheneige\/nain.html\" . }}\nEn plus, je ne pense pas que s'amuser à dénouer des maps complexes soit aussi\npratique que ce que nous permet de faire actuellement .Scratch.SetInMap !\n\n\n\n\nDepuis Hugo 0.38&#160;&#8617;\n\n\nDepuis Hugo 0.43&#160;&#8617; &#8617;\n\n\n",
      "content_html": "<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>\n<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>\n<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>\n<h2 id=\"c-est-quoi-scratch聽\">C'est quoi Scratch ?</h2>\n<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>\n<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>\n<h3 id=\"scratch-set\"><code>.Scratch.Set</code></h3>\n<p><code>Set</code> est utilisé pour mémoriser une valeur voire pour pouvoir surcharger simplement une valeur par la suite.</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ .Scratch.Set <span class=\"hljs-string\">\"salutations\"</span> <span class=\"hljs-string\">\"Bonjour\"</span> }}\n{{ <span class=\"hljs-keyword\">if</span> eq $ciel <span class=\"hljs-string\">\"sombre\"</span> }}\n    {{ .Scratch.Set <span class=\"hljs-string\">\"salutations\"</span> <span class=\"hljs-string\">\"Bonsoir\"</span> }}\n{{ end }}\n\n{{ .Scratch.Get <span class=\"hljs-string\">\"salutations\"</span>}}</code></pre>\n<h3 id=\"scratch-add\"><code>.Scratch.Add</code></h3>\n<p>Cette méthode s'occupe d’ajouter ou de pousser des valeurs multiples dans une\nvariable ou une clef.</p>\n<pre><code class=\"language-go-html-template hljs go\"><span class=\"hljs-comment\">// Pour les chaînes de caractères</span>\n{{ .Scratch.Add <span class=\"hljs-string\">\"salutations\"</span> <span class=\"hljs-string\">\"Bonjour\"</span> }}\n{{ .Scratch.Add <span class=\"hljs-string\">\"salutations\"</span> <span class=\"hljs-string\">\"Bonsoir\"</span> }}\n\n{{ .Scratch.Get <span class=\"hljs-string\">\"salutations\"</span> }}\n<span class=\"hljs-comment\">// Affichera : BonjourBonsoir</span></code></pre>\n<p>Utilisée avec <code>slice</code>, elle permet d’ajouter une ou plusieurs valeurs à un\ntableau.</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ .Scratch.Add <span class=\"hljs-string\">\"salutations\"</span> (slice <span class=\"hljs-string\">\"Bonjour\"</span>) }}\n{{ .Scratch.Add <span class=\"hljs-string\">\"salutations\"</span> (slice <span class=\"hljs-string\">\"Bonsoir\"</span>) }}\n{{ .Scratch.Add <span class=\"hljs-string\">\"salutations\"</span> (slice <span class=\"hljs-string\">\"Aloha\"</span> <span class=\"hljs-string\">\"Buenos dias\"</span>) }}</code></pre>\n<h3 id=\"scratch-get\"><code>.Scratch.Get</code></h3>\n<p>Maintenant récupérons tout ça.</p>\n<pre><code class=\"language-go-html-template hljs go\"><span class=\"hljs-comment\">// Avec la fonction range</span>\n{{ <span class=\"hljs-keyword\">range</span> where .Scratch.Get <span class=\"hljs-string\">\"salutations\"</span> }}\n&lt;ol&gt;\n    &lt;li&gt;\n        {{ . }}\n    &lt;/li&gt;\n&lt;/ol&gt;\n{{ end }}\n<span class=\"hljs-comment\">// ☝️ Affichera une liste ordonnée avec nos 4 salutations.</span>\n\n<span class=\"hljs-comment\">// Ou avec la fonction delimit</span>\n{{ delimit (.Scratch.Get <span class=\"hljs-string\">\"salutations\"</span>), <span class=\"hljs-string\">\", \"</span> }}\n<span class=\"hljs-comment\">// ☝️ Affichera Bonjour, Bonsoir, Aloha, Buenos dias</span></code></pre>\n<h3 id=\"scratch-delete-1\">.Scratch.Delete<sup id=\"fnref1:1\"><a href=\"#fn:1\" class=\"footnote-ref\">1</a></sup></h3>\n<p>Supprime la paire clé/valeur du contexte.\nLors de l'utilisation de <code>.Scratch.Add</code> dans une boucle, <code>.Scratch.Delete</code> est pratique pour réinitialiser une valeur.</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ .Scratch.Delete <span class=\"hljs-string\">\"salutations\"</span> }}</code></pre>\n<h3 id=\"newscratch-2\">newScratch<sup id=\"fnref1:2\"><a href=\"#fn:2\" class=\"footnote-ref\">2</a></sup></h3>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ $headerScratch := newScratch }}\n{{ $headerScratch.Add <span class=\"hljs-string\">\"brand_image\"</span> .Params.image }}</code></pre>\n<h2 id=\"manipuler-des-tableaux-et-des-maps\">Manipuler des tableaux et des maps</h2>\n<h3 id=\"scratch-setinmap\">.Scratch.SetInMap</h3>\n<p>Cette fonction-là permet de cibler la clef d’un tableau et de lui assigner une\nnouvelle valeur.</p>\n<p>Elle prend comme premier paramètre votre clef <code>.Scratch</code>, comme second paramètre\nla clef issue du tableau ou de la map, le troisième étant la valeur que vous\ndéfinissez.</p>\n<p>Si vous ne connaissez pas <a href=\"https://gohugo.io/functions/dict/#readout\" target=\"_blank\" rel=\"noopener noreferrer\">dict</a> je\nvous explique tout ça\n<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>\n<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>) }}\n\n{{ .Scratch.SetInMap <span class=\"hljs-string\">\"salutations\"</span> <span class=\"hljs-string\">\"english\"</span> <span class=\"hljs-string\">\"Howdy 🤠\"</span> }}\n\n<span class=\"hljs-comment\">// Nous avons modifié la valeur de la clef en anglais de Hello à Howdy 🤠 !</span></code></pre>\n<h2 id=\"attention-au-perimetre-et-au-contexte\">Attention au périmètre et au contexte…</h2>\n<p><code>.Scratch</code> n'est disponible que pour l’objet page ou l’objet shortcode. Vous ne\npouvez pas l’utiliser sur un autre élément.</p>\n<p>Souvenez-vous que si vous vous trouvez à l’intérieur d’une boucle <code>range</code> dans\nvotre page d’index, alors le <code>.Scratch</code> de votre page d’index sera <code>$.Scratch</code>\nalors que la page courante que vous traitez dans votre boucle sera <code>.Scratch</code>.</p>\n<p>Retenez également que vous pouvez affecter une paire clef-valeur à <code>.Scratch</code>\ndepuis n'importe où, même dans un fichier partiel du moment que vous lui passez\nle contexte. Heeeeein? Prenons un exemple concret pour illustrer les dangers qui\nvous guettent avec l’utilisation de <code>.Scratch</code> et du contexte.</p>\n<h3 id=\"un-exemple-classe-avec-scratch\">Un exemple classe avec <code>.Scratch</code></h3>\n<p>Je trouve ça bien pratique d’affecter des classes à mon élément <code>body</code> (comme le\nfait WordPress) pour pouvoir faire des ajustements CSS/JavaScript en fonction\nde la page sur laquelle on se trouve.</p>\n<p>Je trouvais ça très fastidieux à faire avec Hugo, jusqu'à ce que je comprenne\ncomment utiliser <code>.Scratch</code>.</p>\n<p>Je veux ajouter une classe CSS <code>rp-body</code> à toutes mes pages ainsi que la valeur\nde <code>.Section</code> à mes classes.</p>\n<p>Et seule la page d’accueil devrait hériter de la classe <code>rp-home</code>.</p>\n<p>Je pourrais écrire ça une bonne foi pour toute, dans un fichier partiel ou un\nfichier de gabarit de page qui comprend l’ouverture de la balise <code>body</code> mais… je\npourrais avoir besoin de cette liste de classes ailleurs dans mon code pour\nréaliser des tours de magie avec ajax. Disons sous forme d’objet JavaScript.</p>\n<p>Comme faire pour créer cette liste, la modifier si je suis sur la page d’accueil\net la stocker dans mon objet <code>.Page</code> pour pouvoir la réutiliser par la suite ?\nPour bien faire, nous allons stocker nos classes dans un tableau.</p>\n<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>\n{{ .Scratch.Add <span class=\"hljs-string\">\"classes\"</span> (slice <span class=\"hljs-string\">\"rp-body\"</span>) }}\n\n<span class=\"hljs-comment\">// Puis ma section. Ce printf me permet d’ajouter la valeur de .Section avec mon préfixe personnalisé.</span>\n{{ .Scratch.Add <span class=\"hljs-string\">\"classes\"</span> (slice (printf <span class=\"hljs-string\">\"rp-%s\"</span> .Section))) }}\n\n<span class=\"hljs-comment\">// Et maintenant sommes nous sur la page d’accueil ?</span>\n{{ <span class=\"hljs-keyword\">if</span> .IsHome }}\n    {{ .Scratch.Add <span class=\"hljs-string\">\"classes\"</span> (slice <span class=\"hljs-string\">\"rp-home\"</span>) }}\n{{end}}\n<span class=\"hljs-comment\">// Est-ce que ce sont les vacances ? 🎄</span>\n{{ <span class=\"hljs-keyword\">if</span> isset .Site.Params <span class=\"hljs-string\">\"season\"</span> }}\n    {{ .Scratch.Add <span class=\"hljs-string\">\"classes\"</span> (slice (printf <span class=\"hljs-string\">\"rp-body--%s\"</span> .Site.Params.season))) }}\n{{ end }}</code></pre>\n<p>Nous pourrions faire bien plus de vérifications et de contorsions, mais en fin\nde compte, nous n'avons plus qu'à écrire dans notre fichier de gabarit ce joli :</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;body class=<span class=\"hljs-string\">'{{ delimit (.Scratch.Get \"classes\") \" \" }}'</span>&gt;</code></pre>\n<p>Et pour JavaScript, nous pouvons créer notre objet à l’endroit où nous en avons\nbesoin.</p>\n<pre><code class=\"language-js hljs javascript\">&lt;script&gt;\n    <span class=\"hljs-keyword\">let</span> bodyClasses = [{{ range .Scratch.Get <span class=\"hljs-string\">\"classes\"</span> }}<span class=\"hljs-string\">\"{{ . }}\"</span>, {{end}}];\n&lt;<span class=\"hljs-regexp\">/script&gt;</span></code></pre>\n<p>Très bon cas de figure, continuons notre chemin.</p>\n<h3 id=\"scratch-dans-un-fichier-partiel\"><code>.Scratch</code> dans un fichier partiel</h3>\n<p>Comme je l’expliquais plus tôt, comme <code>.Scratch</code> fait partie de l’objet page\ngé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\n<code>partial</code>.Déplaçons le bout de code qui stocke nos classes dans un fichier\npartiel pour gagner en lisibilité :</p>\n<pre><code class=\"language-go-html-template hljs go\"><span class=\"hljs-comment\">// partials/scratching/body_classes.html</span>\n{{ .Scratch.Add <span class=\"hljs-string\">\"classes\"</span> (slice <span class=\"hljs-string\">\"rp-body\"</span>) }}\n[… ici le code vu précédemment  …]</code></pre>\n<p>Dans mon fichier de gabarit, je peux maintenant écrire :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"scratching/body_classes.html\"</span> . }}\n&lt;body class=<span class=\"hljs-string\">'{{ delimit (.Scratch.Get \"classes\") \" \" }}'</span>&gt;\n[…]</code></pre>\n<p>Le retour de la fonction <code>.Scratch</code> de la page a été transmis au fichier partiel\nvia le contexte, de manière à pouvoir continuer de le modifier sans avoir à\ntoucher au code ailleurs. En plus ça permet d’avoir des fichiers de gabarits de\npage plus propres !</p>\n<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>\n<p>Quand vous utilisez la fonction <code>range</code> pour boucler sur des éléments, vous ne\npouvez pas lui passer le contexte en paramètre comme on peut le faire avec la\nfonction <code>partial</code>, le contexte que vous manipulez est celui de la boucle, c'est\nbien ce que vous souhaitez.</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ .Scratch.Set <span class=\"hljs-string\">\"section_color\"</span> }}\n{{ <span class=\"hljs-keyword\">range</span> where .Data.Pages}}\n   &lt;h2&gt;{{ .Title }}&lt;/h2&gt;\n     &lt;div class=<span class=\"hljs-string\">\"Child Child--{{ $.Scratch.Get section_color}}\"</span>&gt;\n     […]\n     &lt;div&gt;\n{{ end }}\n<span class=\"hljs-comment\">// Affichera le contenu de section_color.</span>\n\n<span class=\"hljs-comment\">// Alors que…</span>\n{{ <span class=\"hljs-keyword\">range</span> where .Data.Pages }}\n      {{ partial <span class=\"hljs-string\">\"enfant.html\"</span> . }}\n{{ end }}\n\n<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>\n<p>C’est parce que le contexte que nous passons en paramètre de la fonction\n<code>partial</code> est celui de l’élément en cours parcouru grâce à la fonction <code>range</code>,\npas celui de la page dont vous êtes en train de coder le gabarit.</p>\n<p>Très bien me direz-vous, mais comment faire pour accéder au <code>.Scratch</code> de la\npage parente depuis notre fichier partiel ?</p>\n<p>Eh bien, vous pouvez toujours stocker ce qui est retourné par la fonction\n<code>.Scratch</code> de la page dans une variable, pour la passer ensuite en paramètre de\nvotre fichier partiel :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ $indexScratch := .Scratch }}\n  {{ <span class=\"hljs-keyword\">range</span> where .Data.Pages }}\n      {{ partial <span class=\"hljs-string\">\"child.html\"</span> $indexScratch }}\n  {{ end }}</code></pre>\n<p>Dans le fichier partiel on écrira alors :</p>\n<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;\n[…]\n&lt;div&gt;</code></pre>\n<p>Si vous avez également besoin de l’ensemble du contexte de la page que vous êtes\nen train de parcourir dans la boucle, utilisez alors la fonction <code>dict</code> :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ $indexScratch := .Scratch }}\n  {{ <span class=\"hljs-keyword\">range</span> where .Data.Pages }}\n      {{ partial <span class=\"hljs-string\">\"child.html\"</span> (dict <span class=\"hljs-string\">\"indexScratch\"</span> $indexScratch <span class=\"hljs-string\">\"page\"</span> . }}\n  {{ end }}</code></pre>\n<p>Dans le fichier partiel vous pourrez alors écrire :</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;div class=<span class=\"hljs-string\">\"Child Child--{{ .indexScratch.Get section_color}}\"</span>&gt;\n    {{ .page.Content }}\n&lt;div&gt;</code></pre>\n<h3 id=\"scratch-dans-un-fichier-partiel-sans-contexte-de-page\"><em>.Scratch</em> dans un fichier partiel sans contexte de page</h3>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"brand\"</span> .Params.brand }}</code></pre>\n<p>Dans notre fichier partiel nous pouvons toujours faire appel à Scratch :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ $brandScratch := newScratch }}\n{{ $brandScratch.Set <span class=\"hljs-string\">\"brand_image\"</span> <span class=\"hljs-string\">\"default.jpg\"</span> }}\n{{ with .image_src }}\n  {{ $brandScratch.Set <span class=\"hljs-string\">\"brand_image\"</span> <span class=\"hljs-string\">\".\"</span> }}\n{{ end }}\n&lt;div class=<span class=\"hljs-string\">\"brand {{ .class }}\"</span>&gt;\n  &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;\n&lt;/div&gt;</code></pre>\n<h2 id=\"scratch-apres-go-1-11\"><code>.Scratch</code> après Go 1.11</h2>\n<p>Oui, avec la version 11 de Golang nous pouvons maintenant nativement écraser les variables dans les templates Go mais …</p>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">{{ $humeur := <span class=\"hljs-string\">\"Joyeux\"</span> }}\n{{ <span class=\"hljs-keyword\">if</span> $pluie }}\n    {{ $humeur = <span class=\"hljs-string\">\"Grincheux\"</span> }}\n{{ end }}\n{{ 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>\n<p>Utiliser Scratch pour stocker vos variables dans l'objet de Page vous garantit un code propre et réutilisable.</p>\n<h3 id=\"avec-scratch\">Avec <code>.Scratch</code></h3>\n<pre><code class=\"language-go-html-template hljs go\">{{ .Scratch.Set <span class=\"hljs-string\">\"humeur\"</span> <span class=\"hljs-string\">\"Joyeux\"</span> }}\n{{ <span class=\"hljs-keyword\">if</span> $pluie }}\n    {{ .Scratch.Set <span class=\"hljs-string\">\"humeur\"</span> <span class=\"hljs-string\">\"Grincheux\"</span> }}\n{{ end }}\n{{ partial <span class=\"hljs-string\">\"blancheneige/nain.html\"</span> . }}</code></pre>\n<p>En plus, je ne pense pas que s'amuser à dénouer des maps complexes soit aussi\npratique que ce que nous permet de faire actuellement <code>.Scratch.SetInMap</code> !</p>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n<li id=\"fn:2\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "regis"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/02/08/hugo-le-point-sur-le-contexte/",
      "url": "https://jamstatic.fr/2018/02/08/hugo-le-point-sur-le-contexte/",
      "title": "Hugo, le point sur le contexte",
      "summary": "Exemples de gestion du contexte et signification du point dans les templates Hugo.",
      "date_published": "2018-02-08T16:27:39+00:00","content_text": "Le contexte,\nc'est un concept assez perturbant quand on commence à vouloir développer des\nmodèles de page pour Hugo. Il est facile de s'emmêler les pinceaux pour accéder à\nses variables. Au travers\nde quelques exemples très simples, Régis\nPhilibert se propose de nous aider à y\nvoir plus clair.\nMais pourquoi ma variable n'est pas accessible ici et ici ? 🙄\nQuand est un habitué des bons vieux langages de templates où le périmètre\nfonctionnel est rarement un problème, bien comprendre les contraintes de\npérimètre en Go Template n'est pas\ntoujours aisé.\nDans cet article, nous essaierons de comprendre l’impact du périmètre et du\ncontexte à l’intérieur de nos gabarits et de nos fichiers partiels, et comment jongler avec le point 🤹.\nLe contexte et le point\nJ'utilise ici le mot périmètre dans le titre, car c'est la première chose qui\nvient à l’esprit quand on fait face à cette problématique et j'imagine que c'est\nle terme que les gens vont utiliser pour rechercher de l’aide. Mais en\ndéfinitive c'est plutôt du contexte dont nous allons parler ici.\nLe périmètre c'est ce qui est disponible dans une situation donnée dans votre\ncode. À l’intérieur d’une classe ou d’une fonction par exemple.\nMais dans les gabarits d’Hugo, la plupart du temps, vous n'avez accès qu'à un\nseul objet : le contexte. Et il est stocké dans un point.\nOui, ce point-là. {{.}}\nEt vous finissez donc par utiliser les propriétés de cet objet comme ça : \\\n.Title, .Permalink, .IsHome\nLe point de la page\nLe contexte d’origine, celui disponible dans votre fichier de gabarit racine\nbaseof.html et dans les autres fichiers de gabarits sera toujours le contexte\nde la page. En fait, tout ce que vous avez besoin d’afficher dans cette page est\ncontenu dans ce point.\\\n.Title, .Permalink, .Resources et tout ce qui vous chante.\nMême les informations de votre site sont stockées dans le contexte de page à\nl’aide de .Site qui est prêt à l’emploi.\nMais en Go Template dès que vous utilisez une fonction, vous perdez ce\ncontexte, et votre précieux point, votre contexte est remplacé par celui de la\nfonction, qui a son propre… point.\nSi par exemple dans mon gabarit de page j'ai :\nWith\n{{ with .Title }}\n    {{\/* Maintenant le point c’est .Title *\/}}\n    &lt;h1&gt;{{ . }}&lt;\/h1&gt;\n{{ end }}\nÀ l’intérieur de ce with vous n'êtes plus dans le contexte de page. Le\ncontexte, le point, c'est maintenant le titre de votre page. Ici, c'est\nexactement ce que nous voulons !\nRange\nMême chose ici, une fois que vous avez commencé à itérer avec range, le\ncontexte est l’élément actuellement parcouru. Vous perdez le contexte de page au\nprofit du contexte de la fonction range.\n{{ range .Data.Pages }}\n    {{\/* Ici le point est celui de la page 'en cours'. *\/}}\n    {{ .Permalink }}\n{{ end }}\n{{ range .Resources.Match \"gallery\/*\" }}\n    {{\/* Ici le point designe une des images. *\/}}\n    {{ .Permalink }}\n\/\/ {{ end }}\n{{ range (slice \"Hello\" \"Bonjour\" \"Gutten Tag\") }}\n    {{\/* Ici le point désigne cette chaîne de caractères. *\/}}\n    {{ . }}\n{{ end }}\nLe contexte du plus haut niveau de la page 💲\nHeureusement pour nous, Hugo stocke le contexte de page dans un $ donc cela ne\nfait rien si vous vous trouvez au fin fond d’un with ou d’un range, vous\npouvez toujours récupérer le contexte du plus haut niveau de la page.\nUn niveau d’imbrication\n{{ with .Title }}\n    {{\/* Le point désigne .Title *\/}}\n    &lt;h1&gt;{{ . }}&lt;\/h1&gt;\n    {{\/* $ désigne le plus haut niveau de la page *\/}}\n    &lt;h3&gt;From {{ $.Title }}&lt;\/h3&gt;\n{{ end }}\nTrois niveaux d’imbrication\n{{\/* 1. Le point désigne le plus haut niveau de la page (de liste) *\/}}\n&lt;h1&gt;{{ .Title }}&lt;\/h1&gt;\n{{ range .Data.Pages }}\n    &lt;article&gt;\n        {{\/* 2. Le point désigne la page en cours *\/}}\n        &lt;h3&gt;{{ .Title }}&lt;\/h3&gt;\n        &lt;hr&gt;\n        {{ range .Resources.Match \"images\/.*\" }}\n            &lt;figure&gt;\n                {{\/* 3. Le point désigne une de ces ressources *\/}}\n                &lt;img src=\"{{ .Permalink }}\"&gt;\n                {{\/* $ désigne le contexte de haut niveau de la page *\/}}\n                &lt;caption&gt;{{ .Title }} de l’article {{ $.Title }}&lt;\/caption&gt;\n            &lt;\/figure&gt;\n        {{ end }}\n    &lt;\/article&gt;\n{{ end }}\nLes fichiers partiels\nPar défaut, les fichiers partiels ne passent aucun contexte.\\\nMais il suffit d’un seul paramètre pour y remédier. Cet objet sera alors disponible\nà l’intérieur du fichier partiel et sera référencé à l’aide, vous l’aviez deviné,\ndu point.\nDonc pour des fichiers partiels simples, vous n'aurez besoin que du contexte de\npage. Le point de votre page.\n    {{ partial \"page\/head\" . }}\nIci la fonction partial a pour paramètre votre contexte, à priori celui de\nvotre page si vous n'êtes pas dans une boucle range ou dans une condition\nwith ou bien dans un autre fichier partiel.\n    &lt;h1&gt;\n        {{ .Title }}\n    &lt;\/h1&gt;\n    &lt;h3&gt;&lt;time datetime=\"{{ .Date }}\"&gt;{{ dateFormat \"Écrit le 2 January 2006\" .Date }}&lt;\/time&gt;&lt;\/h3&gt;\nMaintenant, imaginons que vous écriviez un fichier partiel pour le rendu de\nvotre image fantaisiste encadrée, vous n'avez besoin que de son chemin, qui\ndevient donc votre contexte.\n{{ partial \"img\" $path }}\nDans le fichier partials\/img.html, on aura donc :\n&lt;figure class=\"Figure Figure--framed\"&gt;\n    &lt;img src=\"{{ . }}\" alt=\"\"&gt;\n&lt;\/figure&gt;\nLe point ici c'est la valeur de $path.\nC’est un exemple tout simple. La plupart du temps, vous aurez besoin de beaucoup\nplus de valeurs.\nPas de souci, nous pouvons utiliser la fonction dict pour passer un ensemble\nd’éléments en paramètre (entier, chaine de caractère, objet)\ndict va créer une map, souvent désignée également comme un tableau\nassociatif.\\\nReportez-vous à la documentation de cette fonction\nou à mon propre article sur le sujet.\n{{ partial \"img\" dict(\"path\" $path \"alt\" \"Nice blue sky\") }}\nLe point va contenir cet objet à l’intérieur du fichier partiel, donc nous\npouvons préfixer nos clefs avec .\n&lt;figure class=\"Figure Figure--framed\"&gt;\n    &lt;img src=\"{{ .path }}\" alt=\"{{ .alt }}\"&gt;\n&lt;\/figure&gt;\nVous pouvez mettre une lettre majuscule à vos clefs pour qu'elles fassent plus\n\"Hugo\" mais j'aime bien mettre tout en minuscules. De cette manière dans un\nfichier partiel j'identifie immédiatement les clefs issues d’un contexte\npersonnalisé de celles issues de celui de la page.\nAccéder au plus haut niveau \\$ depuis un fichier partiel\nContrairement à range et with le contexte de page n'est pas disponible dans\n$.\nPas de problème, nous allons ajouter le contexte de la page à notre dict.\nVous pouvez appeler cette clef importante comme vous voulez, beaucoup de gens\nutilisent \"Page\" pour pouvoir écrire Page.Title. Comme ça vous chante, du\nmoment que vous êtes consistent dans votre nomenclature.\n{{ partial \"img\" dict(\"Page\" . \"path\" $path \"alt\" \"Nice blue sky\") }}\n&lt;figure class=\"Figure Figure--framed\"&gt;\n  &lt;img src=\"{{ .path }}\" alt=\"{{ .alt }} from {{ .Page.Title }}\" \/&gt;\n&lt;\/figure&gt;\nConclusion\nCe point peut vite devenir votre meilleur ami et s'avère bien pratique une fois\nqu'on a appris à jongler avec. Il permet d’écrire du code très lisible même si\nparfois on ne sait plus très bien à quel niveau on se situe.\nIl y a d’autres fonctions qui prennent le contexte, regardez notamment du côté\nde block et template.\nBonne mise au point !",
      "content_html": "<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>,\nc'est un concept assez perturbant quand on commence à vouloir développer des\nmodèles de page pour Hugo. Il est facile de s'emmêler les pinceaux pour accéder à\nses <a href=\"https://golang.org/pkg/text/template/#hdr-Variables\" target=\"_blank\" rel=\"noopener noreferrer\">variables</a>. Au travers\nde quelques exemples très simples, <a href=\"https://regisphilibert.com/tags/hugo/\" target=\"_blank\" rel=\"noopener noreferrer\">Régis\nPhilibert</a> se propose de nous aider à y\nvoir plus clair.</p></aside>\n<p><strong>Mais pourquoi ma variable n'est pas accessible ici et ici ?</strong> 🙄</p>\n<p>Quand est un habitué des bons vieux langages de templates où le périmètre\nfonctionnel est rarement un problème, bien comprendre les contraintes de\npérimètre en <a href=\"https://golang.org/pkg/html/template/\" target=\"_blank\" rel=\"noopener noreferrer\">Go Template</a> n'est pas\ntoujours aisé.</p>\n<p>Dans cet article, nous essaierons de comprendre l’impact du périmètre et du\ncontexte à l’intérieur de nos gabarits et de nos fichiers partiels, et comment jongler avec le point 🤹.</p>\n<h2 id=\"le-contexte-et-le-point\">Le contexte et le point</h2>\n<p>J'utilise ici le mot <em>périmètre</em> dans le titre, car c'est la première chose qui\nvient à l’esprit quand on fait face à cette problématique et j'imagine que c'est\nle terme que les gens vont utiliser pour rechercher de l’aide. Mais en\ndéfinitive c'est plutôt du <em>contexte</em> dont nous allons parler ici.</p>\n<p>Le périmètre c'est ce qui est disponible dans une situation donnée dans votre\ncode. À l’intérieur d’une classe ou d’une fonction par exemple.</p>\n<p>Mais dans les gabarits d’Hugo, la plupart du temps, vous n'avez accès qu'à un\nseul objet : le <strong>contexte</strong>. Et il est stocké dans un point.</p>\n<p>Oui, ce point-là. <code>{{.}}</code></p>\n<p>Et vous finissez donc par utiliser les propriétés de cet objet comme ça : \\\n<code>.Title</code>, <code>.Permalink</code>, <code>.IsHome</code></p>\n<h2 id=\"le-point-de-la-page\">Le point de la page</h2>\n<p>Le contexte d’origine, celui disponible dans votre fichier de gabarit racine\n<code>baseof.html</code> et dans les autres fichiers de gabarits sera toujours le contexte\nde la page. En fait, tout ce que vous avez besoin d’afficher dans cette page est\ncontenu dans ce point.\\\n<code>.Title</code>, <code>.Permalink</code>, <code>.Resources</code> et tout ce qui vous chante.</p>\n<p>Même les informations de votre site sont stockées dans le contexte de page à\nl’aide de <code>.Site</code> qui est prêt à l’emploi.</p>\n<p>Mais en <em>Go Template</em> dès que vous utilisez une fonction, vous perdez ce\ncontexte, et votre précieux point, votre contexte est remplacé par celui de la\nfonction, qui a son propre… point.</p>\n<p>Si par exemple dans mon gabarit de page j'ai :</p>\n<h3 id=\"with\">With</h3>\n<pre><code class=\"language-go-html-template hljs go\">{{ with .Title }}\n    {{<span class=\"hljs-comment\">/* Maintenant le point c’est .Title */</span>}}\n    &lt;h1&gt;{{ . }}&lt;/h1&gt;\n{{ end }}</code></pre>\n<p>À l’intérieur de ce <code>with</code> vous n'êtes plus dans le contexte de page. Le\ncontexte, le point, c'est maintenant le titre de votre page. Ici, c'est\nexactement ce que nous voulons !</p>\n<h3 id=\"range\">Range</h3>\n<p>Même chose ici, une fois que vous avez commencé à itérer avec <code>range</code>, le\ncontexte est l’élément actuellement parcouru. Vous perdez le contexte de page au\nprofit du contexte de la fonction <code>range</code>.</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">range</span> .Data.Pages }}\n    {{<span class=\"hljs-comment\">/* Ici le point est celui de la page 'en cours'. */</span>}}\n    {{ .Permalink }}\n{{ end }}</code></pre>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">range</span> .Resources.Match <span class=\"hljs-string\">\"gallery/*\"</span> }}\n    {{<span class=\"hljs-comment\">/* Ici le point designe une des images. */</span>}}\n    {{ .Permalink }}\n<span class=\"hljs-comment\">// {{ end }}</span></code></pre>\n<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>) }}\n    {{<span class=\"hljs-comment\">/* Ici le point désigne cette chaîne de caractères. */</span>}}\n    {{ . }}\n{{ end }}</code></pre>\n<h3 id=\"le-contexte-du-plus-haut-niveau-de-la-page-rџ-I\">Le contexte du plus haut niveau de la page 💲</h3>\n<p>Heureusement pour nous, Hugo stocke le contexte de page dans un <code>$</code> donc cela ne\nfait rien si vous vous trouvez au fin fond d’un <code>with</code> ou d’un <code>range</code>, vous\npouvez toujours récupérer le contexte du plus haut niveau de la page.</p>\n<h4 id=\"un-niveau-d-imbrication\">Un niveau d’imbrication</h4>\n<pre><code class=\"language-go-html-template hljs go\">{{ with .Title }}\n    {{<span class=\"hljs-comment\">/* Le point désigne .Title */</span>}}\n    &lt;h1&gt;{{ . }}&lt;/h1&gt;\n    {{<span class=\"hljs-comment\">/* $ désigne le plus haut niveau de la page */</span>}}\n    &lt;h3&gt;From {{ $.Title }}&lt;/h3&gt;\n{{ end }}</code></pre>\n<h4 id=\"trois-niveaux-d-imbrication\">Trois niveaux d’imbrication</h4>\n<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>}}\n&lt;h1&gt;{{ .Title }}&lt;/h1&gt;\n{{ <span class=\"hljs-keyword\">range</span> .Data.Pages }}\n    &lt;article&gt;\n        {{<span class=\"hljs-comment\">/* 2. Le point désigne la page en cours */</span>}}\n        &lt;h3&gt;{{ .Title }}&lt;/h3&gt;\n        &lt;hr&gt;\n        {{ <span class=\"hljs-keyword\">range</span> .Resources.Match <span class=\"hljs-string\">\"images/.*\"</span> }}\n            &lt;figure&gt;\n                {{<span class=\"hljs-comment\">/* 3. Le point désigne une de ces ressources */</span>}}\n                &lt;img src=<span class=\"hljs-string\">\"{{ .Permalink }}\"</span>&gt;\n                {{<span class=\"hljs-comment\">/* $ désigne le contexte de haut niveau de la page */</span>}}\n                &lt;caption&gt;{{ .Title }} de l’article {{ $.Title }}&lt;/caption&gt;\n            &lt;/figure&gt;\n        {{ end }}\n    &lt;/article&gt;\n{{ end }}</code></pre>\n<h2 id=\"les-fichiers-partiels\">Les fichiers partiels</h2>\n<p>Par défaut, les fichiers partiels ne passent aucun contexte.\\\nMais il suffit d’un seul paramètre pour y remédier. Cet objet sera alors disponible\nà l’intérieur du fichier partiel et sera référencé à l’aide, vous l’aviez deviné,\ndu point.</p>\n<p>Donc pour des fichiers partiels simples, vous n'aurez besoin que du contexte de\npage. Le <strong>point</strong> de votre page.</p>\n<pre><code class=\"language-go-html-template hljs go\">    {{ partial <span class=\"hljs-string\">\"page/head\"</span> . }}</code></pre>\n<p>Ici la fonction <code>partial</code> a pour paramètre votre contexte, à priori celui de\nvotre page si vous n'êtes pas dans une boucle <code>range</code> ou dans une condition\n<code>with</code> ou bien dans un autre fichier partiel.</p>\n<pre><code class=\"language-go-html-template hljs go\">    &lt;h1&gt;\n        {{ .Title }}\n    &lt;/h1&gt;\n    &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>\n<p>Maintenant, imaginons que vous écriviez un fichier partiel pour le rendu de\nvotre image fantaisiste encadrée, vous n'avez besoin que de son chemin, qui\ndevient donc votre contexte.</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"img\"</span> $path }}</code></pre>\n<p>Dans le fichier <code>partials/img.html</code>, on aura donc :</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;figure class=<span class=\"hljs-string\">\"Figure Figure--framed\"</span>&gt;\n    &lt;img src=<span class=\"hljs-string\">\"{{ . }}\"</span> alt=<span class=\"hljs-string\">\"\"</span>&gt;\n&lt;/figure&gt;</code></pre>\n<p>Le point ici c'est la valeur de <code>$path</code>.</p>\n<p>C’est un exemple tout simple. La plupart du temps, vous aurez besoin de beaucoup\nplus de valeurs.</p>\n<p>Pas de souci, nous pouvons utiliser la fonction <code>dict</code> pour passer un ensemble\nd’éléments en paramètre (entier, chaine de caractère, objet)</p>\n<p><code>dict</code> va créer une <em>map</em>, souvent désignée également comme un tableau\nassociatif.\\\nReportez-vous à la <a href=\"https://gohugo.io/functions/dict\" target=\"_blank\" rel=\"noopener noreferrer\">documentation de cette fonction</a>\nou à 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>\n<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>\n<p>Le point va contenir cet objet à l’intérieur du fichier partiel, donc nous\npouvons préfixer nos clefs avec <code>.</code></p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;figure class=<span class=\"hljs-string\">\"Figure Figure--framed\"</span>&gt;\n    &lt;img src=<span class=\"hljs-string\">\"{{ .path }}\"</span> alt=<span class=\"hljs-string\">\"{{ .alt }}\"</span>&gt;\n&lt;/figure&gt;</code></pre>\n<p>Vous pouvez mettre une lettre majuscule à vos clefs pour qu'elles fassent plus\n\"Hugo\" mais j'aime bien mettre tout en minuscules. De cette manière dans un\nfichier partiel j'identifie immédiatement les clefs issues d’un contexte\npersonnalisé de celles issues de celui de la page.</p>\n<h3 id=\"acceder-au-plus-haut-niveau-depuis-un-fichier-partiel\">Accéder au plus haut niveau \\$ depuis un fichier partiel</h3>\n<p>Contrairement à <code>range</code> et <code>with</code> le contexte de page n'est pas disponible dans\n<code>$</code>.</p>\n<p>Pas de problème, nous allons ajouter le contexte de la page à notre <code>dict</code>.</p>\n<p>Vous pouvez appeler cette clef importante comme vous voulez, beaucoup de gens\nutilisent \"Page\" pour pouvoir écrire <code>Page.Title</code>. Comme ça vous chante, du\nmoment que vous êtes consistent dans votre nomenclature.</p>\n<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>\n<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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">figure</span>&gt;</span></code></pre>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>Ce point peut vite devenir votre meilleur ami et s'avère bien pratique une fois\nqu'on a appris à jongler avec. Il permet d’écrire du code très lisible même si\nparfois on ne sait plus très bien à quel niveau on se situe.</p>\n<p>Il y a d’autres fonctions qui prennent le contexte, regardez notamment du côté\nde <code>block</code> et <code>template</code>.</p>\n<p>Bonne mise au point !</p>",
      "authors": [
        {
          "name": "regis"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/01/24/eleventy-generateur-statique-simple/",
      "url": "https://jamstatic.fr/2018/01/24/eleventy-generateur-statique-simple/",
      "title": "Un site web simple avec le plus simple des générateurs de site statique",
      "summary": "Présentation d’Eleventy, le générateur de site statique le plus simple et le plus intuitif.",
      "date_published": "2018-01-24T19:40:44+00:00","content_text": "Il existe des centaines de générateurs de site statique et il en arrive toujours de nouveaux. Après avoir longtemps utilisé Jekyll, Zach Leat, développeur front-end chez Filament Group, a décidé de s'inspirer des principes de Jekyll pour les porter et les étendre grâce à l’écosystème de npm 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 (Liquid par défaut comme dans Jekyll et aussi tout plein d’autres qu'on peut mélanger à loisir bien connus des développeurs JS) tout en leur offrant la puissance de npm. Zach nous propose un premier aperçu de son fonctionnement.\n\n\n\n\n\n\nPhoto de Jeremy Bishop\n\nVoici Eleventy, 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. Faites en sorte que votre site dure 10 ans, pas 10 mois.\nInstallation\n\n\nSi ce n'est pas déjà fait, installez node.js et npm (ils sont dispos sous la forme d’un seul et unique paquet). Eleventy ne fonctionne qu’à partir de node --version 8.0.0 ou plus.\n\n\nEnsuite, installez l’utilitaire en ligne de commande, disponible sur npm : npm install -g @11ty\/eleventy\n\n\nEn avant\nFaisons un site web pour notre collection d’images GIF. Une interface pour notre propre domaine bukk.it. Appelons ça Giffleball.\nLe code source de la première partie de ce tutoriel est disponible sur GitHub.\nCréation des fichiers\nCréons un dossier pour notre tout nouveau site web.\nmkdir giffleball\nAjoutons quelques images à notre site. Voici une sélection d’images d’oiseaux tirée de l’honorable site bukk.it.\n\n\n\n\n\n\n\n\n\n\n\nSauvegardez ces images dans un dossier img à l’intérieur de notre répertoire giffleball.\ngiffleball\/\n  img\/???.jpg      (nouveau)\n  img\/….jpg        (nouveau)\n  img\/parrot.gif   (nouveau)\nCréation d’un gabarit de page\nFaisons un gabarit de page ! Créez un fichier nommé index.html dans le répertoire giffleball.\ngiffleball\/\n  index.html       (nouveau)\n  img\/???.jpg\n  img\/….jpg\n  img\/parrot.gif\nCréons une liste avec des liens vers nos images GIF dans le fichier index.html :\n&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"utf-8\" \/&gt;\n    &lt;title&gt;Giffleball&lt;\/title&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;h1&gt;Giffleball&lt;\/h1&gt;\n    &lt;ul&gt;\n      &lt;li&gt;&lt;a href=\"img\/???.jpg\"&gt;???.jpg&lt;\/a&gt;&lt;\/li&gt;\n      &lt;li&gt;&lt;a href=\"img\/….jpg\"&gt;….jpg&lt;\/a&gt;&lt;\/li&gt;\n      &lt;li&gt;&lt;a href=\"img\/parrot.gif\"&gt;parrot.gif&lt;\/a&gt;&lt;\/li&gt;\n    &lt;\/ul&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;\nJusqu'ici, rien d’extraordinaire. Mais nous pouvons déjà lancer eleventy et générer notre site. Nous passerons en option les extensions de fichier que nous voulons voir eleventy traiter :\n~ cd giffleball\n~\/giffleball $ eleventy --formats=html,gif,jpg\nWriting _site\/index.html from .\/index.html.\nWrote 1 file in 0.07 seconds\nCela a pour effet de créer un nouveau site web dans le répertoire _site. 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 --output :\n~\/giffleball $ eleventy --output=ailleurs\nWriting ailleurs\/index.html from .\/index.html.\nWrote 1 file in 0.07 seconds\nBasons-nous sur des données\nOK, jusqu'ici nous aurions simplement pu charger notre fichier index.html 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 eleventy. Déplaçons certaines données de la page dans notre front matter :\n---\nsiteTitle: Giffleball\nimages:\n  - ???.jpg\n  - ….jpg\n  - parrot.gif\n---\n\n&lt;!doctype html&gt;\n\n&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"utf-8\"&gt;\n    &lt;title&gt;{{ siteTitle }}&lt;\/title&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;h1&gt;{{ siteTitle }}&lt;\/h1&gt;\n    &lt;ul&gt;\n    {% for filename in images %}\n      &lt;li&gt;&lt;a href=\"img\/{{ filename }}\"&gt;{{ filename }}&lt;\/a&gt;&lt;\/li&gt;\n    {% endfor %}\n    &lt;\/ul&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;\nNous avons ajouté le titre du site (utilisé à deux endroits) ainsi que la liste des images dans notre front matter.\nPar défaut dans Eleventy, le moteur du rendu liquid est disponible pour les fichiers HTML et les fichiers Markdown. Eleventy supporte une large gamme de moteurs de rendu (jetez un œil sur la liste complète) qui sont disponibles lorsque vous utilisez une extension de fichier spécifique. Par exemple notre fichier index.html aurait pu s'appeler index.liquid et le fonctionnement aurait été le même :\n~\/giffleball $ mv index.html index.liquid\n~\/giffleball $ eleventy --formats=liquid,html,jpg,gif\nWriting _site\/index.html from .\/index.liquid.\nWrote 1 file in 0.07 seconds\nBien 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 README).\nL'utilisation d’un moteur de rendu présente plusieurs avantages :\n\n\nModifier 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.\n\n\nModifier 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 &lt;li&gt; individuellement. Trois passent encore mais vous imaginez si notre site listait 300 images ?\n\n\nLes 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 ???.jpg. 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 : un filtre url_encode. Mettons notre gabarit à jour pour en bénéficier :\n\n\n{% for filename in images %}\n&lt;li&gt;&lt;a href=\"img\/{{ filename | url_encode }}\"&gt;{{ filename }}&lt;\/a&gt;&lt;\/li&gt;\n{% endfor %}\nAh c'est bien mieux. Ça marche nickel.\nJ'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.\nLe code source de la deuxième partie de ce tutoriel est disponible sur GitHub.\nAjoutons un filtre\nFaisons 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 .eleventy.js — créons en un. Il devrait ressembler à ça :\nmodule.exports = function (eleventyConfig) {};\nSi vous ne le nommez pas .eleventy.js, chaque fois que vous allez lancer la commande eleventy il faudra lui passer le nom du fichier de configuration en option à l’aide de --config=maConfig.js. C’est bien plus simple de s'en tenir au nom par défaut.\nAjoutons notre filtre à l’aide de la méthode .addFilter. Appelons-le filesize et commençons par lui faire retourner un texte tout bête :\nmodule.exports = function (eleventyConfig) {\n  eleventyConfig.addFilter(\"filesize\", function (path) {\n    return \"0 KB\";\n  });\n};\nBien entendu, notre filtre n'est pas bon, car nous nous contentons de retourner \"0 KB\" à chaque fois. Mais vérifions d’abord qu'il marche.\nOuvrons notre modèle index.html et regardons à quoi ressemble notre boucle pour le moment :\n&lt;ul&gt;\n  {% for filename in images %}\n  &lt;li&gt;&lt;a href=\"img\/{{ filename | url_encode }}\"&gt;{{ filename }}&lt;\/a&gt;&lt;\/li&gt;\n  {% endfor %}\n&lt;\/ul&gt;\nVous avez fait attention à la façon dont nous avons utilisé le filtre natif url_encode fourni par le moteur de rendu Liquid ? Maintenant que nous avons créé le nôtre, ajoutons un appel à notre petit filtre maison, comme ceci :\n&lt;ul&gt;\n  {% for filename in images %} {% capture path %}img\/{{ filename }}{% endcapture\n  %}\n  &lt;li&gt;\n    &lt;a href=\"img\/{{ filename | url_encode }}\"&gt;{{ filename }}&lt;\/a&gt; {{ path |\n    filesize }}\n  &lt;\/li&gt;\n  {% endfor %}\n&lt;\/ul&gt;\nBien entendu, la magie a lieu dans {{ path | filesize }}. Mais notez comment nous utilisons la balise {% capture %} de Liquid pour créer une nouvelle variable path avec Liquid, que nous passons ensuite à notre filtre.\nMaintenant, lançons eleventy pour générer les fichiers.\n~\/giffleball $ eleventy --formats=html,gif,jpg\nWriting _site\/index.html from .\/index.html.\nWrote 1 file in 0.07 seconds\nCela va générer le code suivant dans le fichier _site\/index.html (ici nous ne montrons que le rendu de la liste et pas le fichier HTML entier pour faire court) :\n&lt;ul&gt;\n  &lt;li&gt;&lt;a href=\"img\/%3F%3F%3F.jpg\"&gt;???.jpg&lt;\/a&gt; 0 KB&lt;\/li&gt;\n  &lt;li&gt;&lt;a href=\"img\/%E2%80%A6.jpg\"&gt;….jpg&lt;\/a&gt; 0 KB&lt;\/li&gt;\n  &lt;li&gt;&lt;a href=\"img\/parrot.gif\"&gt;parrot.gif&lt;\/a&gt; 0 KB&lt;\/li&gt;\n&lt;\/ul&gt;\nOK, c'est presque ça — mais c'est quoi tous ces espacements ? (Notez que c'est une question purement rhétorique à laquelle je vais m'empresser de répondre tout de suite.) 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 {%- à la place de {% pour supprimer l’espacement avant la balise Liquid. Et indépendamment on peut aussi utiliser -%} à la place de %} à 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 {%- au début. Il est important pour moi d’avoir une vue du code source qui soit propre, alors nettoyons tout ça :\n&lt;ul&gt;\n  {%- for filename in images %}\n  {%- capture path %}img\/{{ filename }}{% endcapture %}\n  &lt;li&gt;&lt;a href=\"img\/{{ filename | url_encode }}\"&gt;{{ filename }}&lt;\/a&gt; {{ path | filesize }}&lt;\/li&gt;\n  {%- endfor %}\n&lt;\/ul&gt;\nCe qui produit :\n&lt;ul&gt;\n  &lt;li&gt;&lt;a href=\"img\/%3F%3F%3F.jpg\"&gt;???.jpg&lt;\/a&gt; 0 KB&lt;\/li&gt;\n  &lt;li&gt;&lt;a href=\"img\/%E2%80%A6.jpg\"&gt;….jpg&lt;\/a&gt; 0 KB&lt;\/li&gt;\n  &lt;li&gt;&lt;a href=\"img\/parrot.gif\"&gt;parrot.gif&lt;\/a&gt; 0 KB&lt;\/li&gt;\n&lt;\/ul&gt;\nMagnifique.\nC’est encore un peu tôt pour nous réjouir, notre filtre n'est pas fini\nOK, faisons en sorte que notre filtre serve à quelque chose plutôt que de simplement retourner systématiquement \"0 KB\". Modifiez votre fichier .eleventy.js comme ceci :\nconst fs = require(\"fs\");\n\nmodule.exports = function (eleventyConfig) {\n  eleventyConfig.addFilter(\"filesize\", function (path) {\n    let stat = fs.statSync(path);\n    if (stat) {\n      return (stat.size \/ 1024).toFixed(2) + \" KB\";\n    }\n    return;\n    (\"\");\n  });\n};\nC’est la manière la plus simple de le faire marcher, ça n'ajoute aucune nouvelle dépendance lors d’un npm install.\nAllons plus loin à l’aide de NPM\nUn 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 npm, lancez cette commande pour générer un fichier package.json pour notre projet :\n~\/giffleball $ npm init -f\nNous pouvons maintenant installer des modules cools à notre projet, comme file-size pour des tailles de fichiers plus lisibles.\n~\/giffleball $ npm install --save file-size\n+ file-size@1.0.0\nadded 1 package in 1.491s\nUtilisons-le pour coder notre filtrer dans le fichier .eleventy.js:\nconst fs = require(\"fs\");\nconst filesize = require(\"file-size\");\n\nmodule.exports = function (eleventyConfig) {\n  eleventyConfig.addFilter(\"filesize\", function (path) {\n    let stat = fs.statSync(path);\n    if (stat) {\n      return filesize(stat.size).human();\n    }\n    return \"\";\n  });\n};\nCe qui nous donne :\n&lt;ul&gt;\n  &lt;li&gt;&lt;a href=\"img\/%3F%3F%3F.jpg\"&gt;???.jpg&lt;\/a&gt; 44.52 KiB&lt;\/li&gt;\n  &lt;li&gt;&lt;a href=\"img\/%E2%80%A6.jpg\"&gt;….jpg&lt;\/a&gt; 55.39 KiB&lt;\/li&gt;\n  &lt;li&gt;&lt;a href=\"img\/parrot.gif\"&gt;parrot.gif&lt;\/a&gt; 2.05 KiB&lt;\/li&gt;\n&lt;\/ul&gt;\nFélicitations ! Vous avez ajouté un filtre et tiré profit du vaste et immense écosystème NPM.\nJ'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.\nÀ suivre\nDans 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.",
      "content_html": "<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>\n<figure>\n<picture title=\"Photo de Jeremy Bishop\">\n<source type=\"image/webp\" srcset=\"/images/2018-01-24_eleventy-generateur-statique-simple/1a45L3MbFiiPn_B_ZZrLvxg.0e6dcbc0f4a223e7bbcc14d68e16ab41.webp\" width=\"700\" height=\"418\">\n<source type=\"image/avif\" srcset=\"/images/2018-01-24_eleventy-generateur-statique-simple/1a45L3MbFiiPn_B_ZZrLvxg.0e6dcbc0f4a223e7bbcc14d68e16ab41.avif\" width=\"700\" height=\"418\">\n<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;\">\n</picture>\n<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>\n</figure>\n<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>\n<h3 id=\"installation\">Installation</h3>\n<ol>\n<li>\n<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>\n</li>\n<li>\n<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>\n</li>\n</ol>\n<h3 id=\"en-avant\">En avant</h3>\n<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>\n<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>\n<h4 id=\"creation-des-fichiers\">Création des fichiers</h4>\n<p>Créons un dossier pour notre tout nouveau site web.</p>\n<pre><code class=\"language-sh hljs bash\">mkdir giffleball</code></pre>\n<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>\n<picture>\n<source type=\"image/webp\" srcset=\"/thumbnails/350x/images/2018-01-24_eleventy-generateur-statique-simple/19_SoEA-aNHeVF8QKzEUhiw.45020dffa1e52e4cc52ad7947c3187e3.webp\" width=\"350\" height=\"251\">\n<source type=\"image/avif\" srcset=\"/thumbnails/350x/images/2018-01-24_eleventy-generateur-statique-simple/19_SoEA-aNHeVF8QKzEUhiw.45020dffa1e52e4cc52ad7947c3187e3.avif\" width=\"350\" height=\"251\">\n<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;\">\n</picture>\n<picture>\n<source type=\"image/webp\" srcset=\"/thumbnails/350x/images/2018-01-24_eleventy-generateur-statique-simple/1JIkW2kqH59V9KaKsjEKqdQ.b0aebf9646e395aad3518e7f64fa29b1.webp\" width=\"350\" height=\"293\">\n<source type=\"image/avif\" srcset=\"/thumbnails/350x/images/2018-01-24_eleventy-generateur-statique-simple/1JIkW2kqH59V9KaKsjEKqdQ.b0aebf9646e395aad3518e7f64fa29b1.avif\" width=\"350\" height=\"293\">\n<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;\">\n</picture>\n<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=\"\">\n<p>Sauvegardez ces images dans un dossier <code>img</code> à l’intérieur de notre répertoire <code>giffleball</code>.</p>\n<pre><code class=\"language-sh hljs bash\">giffleball/\n  img/???.jpg      (nouveau)\n  img/….jpg        (nouveau)\n  img/parrot.gif   (nouveau)</code></pre>\n<h4 id=\"creation-d-un-gabarit-de-page\">Création d’un gabarit de page</h4>\n<p>Faisons un gabarit de page ! Créez un fichier nommé <code>index.html</code> dans le répertoire <code>giffleball</code>.</p>\n<pre><code class=\"language-sh hljs bash\">giffleball/\n  index.html       (nouveau)\n  img/???.jpg\n  img/….jpg\n  img/parrot.gif</code></pre>\n<p>Créons une liste avec des liens vers nos images GIF dans le fichier <code>index.html</code> :</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-meta\">&lt;!DOCTYPE <span class=\"hljs-meta-keyword\">html</span>&gt;</span>\n<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>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span>\n    <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>\n    <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>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span>\n    <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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n      <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>\n      <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>\n      <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>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">~ <span class=\"hljs-built_in\">cd</span> giffleball\n~/giffleball $ eleventy --formats=html,gif,jpg\nWriting _site/index.html from ./index.html.\nWrote 1 file <span class=\"hljs-keyword\">in</span> 0.07 seconds</code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">~/giffleball $ eleventy --output=ailleurs\nWriting ailleurs/index.html from ./index.html.\nWrote 1 file <span class=\"hljs-keyword\">in</span> 0.07 seconds</code></pre>\n<h3 id=\"basons-nous-sur-des-donnees\">Basons-nous sur des données</h3>\n<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>\n<pre><code class=\"language-markdown hljs markdown\">---\nsiteTitle: Giffleball\nimages:\n<span class=\"hljs-bullet\">  - </span>???.jpg\n<span class=\"hljs-bullet\">  - </span>….jpg\n<span class=\"hljs-section\">  - parrot.gif\n---</span>\n\n<span class=\"xml\"><span class=\"hljs-meta\">&lt;!doctype <span class=\"hljs-meta-keyword\">html</span>&gt;</span></span>\n\n<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>\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span></span>\n<span class=\"hljs-code\">    &lt;meta charset=\"utf-8\"&gt;</span>\n<span class=\"hljs-code\">    &lt;title&gt;{{ siteTitle }}&lt;/title&gt;</span>\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span></span>\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span></span>\n<span class=\"hljs-code\">    &lt;h1&gt;{{ siteTitle }}&lt;/h1&gt;</span>\n<span class=\"hljs-code\">    &lt;ul&gt;</span>\n<span class=\"hljs-code\">    {% for filename in images %}</span>\n<span class=\"hljs-code\">      &lt;li&gt;&lt;a href=\"img/{{ filename }}\"&gt;{{ filename }}&lt;/a&gt;&lt;/li&gt;</span>\n<span class=\"hljs-code\">    {% endfor %}</span>\n<span class=\"hljs-code\">    &lt;/ul&gt;</span>\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span></span>\n<span class=\"xml\"><span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></span></code></pre>\n<p>Nous avons ajouté le titre du site (utilisé à deux endroits) ainsi que la liste des images dans notre front matter.</p>\n<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>\n<pre><code class=\"language-sh hljs bash\">~/giffleball $ mv index.html index.liquid\n~/giffleball $ eleventy --formats=liquid,html,jpg,gif\nWriting _site/index.html from ./index.liquid.\nWrote 1 file <span class=\"hljs-keyword\">in</span> 0.07 seconds</code></pre>\n<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>\n<p>L'utilisation d’un moteur de rendu présente plusieurs avantages :</p>\n<ol>\n<li>\n<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>\n</li>\n<li>\n<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>\n</li>\n<li>\n<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>\n</li>\n</ol>\n<pre><code class=\"language-html hljs xml\">{% for filename in images %}\n<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>\n{% endfor %}</code></pre>\n<p>Ah c'est bien mieux. Ça marche nickel.</p>\n<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>\n<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>\n<h3 id=\"ajoutons-un-filtre\">Ajoutons un filtre</h3>\n<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>\n<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>\n<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>\n<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>\n<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>{\n  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>{\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-string\">\"0 KB\"</span>;\n  });\n};</code></pre>\n<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>\n<p>Ouvrons notre modèle <code>index.html</code> et regardons à quoi ressemble notre boucle pour le moment :</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n  {% for filename in images %}\n  <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>\n  {% endfor %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span></code></pre>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n  {% for filename in images %} {% capture path %}img/{{ filename }}{% endcapture\n  %}\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li</span>&gt;</span>\n    <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 |\n    filesize }}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n  {% endfor %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span></code></pre>\n<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>\n<p>Maintenant, lançons <code>eleventy</code> pour générer les fichiers.</p>\n<pre><code class=\"language-sh hljs bash\">~/giffleball $ eleventy --formats=html,gif,jpg\nWriting _site/index.html from ./index.html.\nWrote 1 file <span class=\"hljs-keyword\">in</span> 0.07 seconds</code></pre>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n  <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>\n  <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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span></code></pre>\n<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>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n  </span><span class=\"hljs-template-tag\">{%- <span class=\"hljs-name\"><span class=\"hljs-keyword\">for</span></span> filename in images %}</span><span class=\"xml\">\n  </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\">\n  <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>\n  </span><span class=\"hljs-template-tag\">{%- <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span><span class=\"xml\">\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span></span></code></pre>\n<p>Ce qui produit :</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n  <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>\n  <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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span></code></pre>\n<p>Magnifique.</p>\n<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>\n<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>\n<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>);\n\n<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>{\n  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>{\n    <span class=\"hljs-keyword\">let</span> stat = fs.statSync(path);\n    <span class=\"hljs-keyword\">if</span> (stat) {\n      <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>;\n    }\n    <span class=\"hljs-keyword\">return</span>;\n    (<span class=\"hljs-string\">\"\"</span>);\n  });\n};</code></pre>\n<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>\n<h3 id=\"allons-plus-loin-a-l-aide-de-npm\">Allons plus loin à l’aide de NPM</h3>\n<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>\n<pre><code class=\"language-sh hljs bash\">~/giffleball $ npm init -f</code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">~/giffleball $ npm install --save file-size\n+ file-size@1.0.0\nadded 1 package <span class=\"hljs-keyword\">in</span> 1.491s</code></pre>\n<p>Utilisons-le pour coder notre filtrer dans le fichier <code>.eleventy.js</code>:</p>\n<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>);\n<span class=\"hljs-keyword\">const</span> filesize = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">\"file-size\"</span>);\n\n<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>{\n  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>{\n    <span class=\"hljs-keyword\">let</span> stat = fs.statSync(path);\n    <span class=\"hljs-keyword\">if</span> (stat) {\n      <span class=\"hljs-keyword\">return</span> filesize(stat.size).human();\n    }\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-string\">\"\"</span>;\n  });\n};</code></pre>\n<p>Ce qui nous donne :</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul</span>&gt;</span>\n  <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>\n  <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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span></code></pre>\n<p>Félicitations ! Vous avez ajouté un filtre et tiré profit du vaste et immense écosystème NPM.</p>\n<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>\n<h4 id=\"a-suivre\">À suivre</h4>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/01/09/wordpress-comme-cms-pour-vos-sites-statiques/",
      "url": "https://jamstatic.fr/2018/01/09/wordpress-comme-cms-pour-vos-sites-statiques/",
      "title": "Utiliser WordPress comme CMS pour vos sites statiques",
      "summary": "Grâce à son API REST WordPress fait aussi CMS headless. Stefan Baumgartner montre comment récupérer les contenus pour générer un site statique avec Metalsmith.",
      "date_published": "2018-01-09T14:50:46+00:00","content_text": "La mode est au CMS headless,\ncomprenez au découplage du back et du front. WordPress n'échappe pas à la règle.\nMême si son API REST ne permet pas encore de faire tout ce qu'on voudrait, il\nest tout à fait possible d’aller récupérer les contenus entrés par les\nutilisateurs dans l’interface d’administration qu'ils affectionnent tant pour\nensuite les passer à la moulinette d’un générateur de site statique. Stefan\nBaumgartner a testé pour vous avec Metalsmith,\nvoici comment il a procédé.\nLa toute-puissante JAMStack offre des sites web statiques rapides\net sécurisés, et avec des systèmes de gestion de contenu headless ils deviennent même faciles à éditer ! Toutefois il peut\narriver que de temps à autre, vous vous retrouviez devant un blog WordPress avec\ntellement d’articles (et d’auteurs qui ont peur du changement !) pour que la\nraison vous pousse à ne pas le migrer. Mais WordPress aussi fonctionne en\nheadless. D'ailleurs, le propre service d’hébergement de WordPress communique\navec le core de WordPress uniquement au travers de son API, l’interface\nd’édition fait partie de la toute nouvelle et belle application\nCalypso.\nUn des gros avantages quand on utilise un générateur de site statique, c'est que\ngénéralement il se moque de la provenance de votre contenu. Utilisons donc\nl’attrayante API REST de WordPress pour récupérer un\npeu de contenu et générer des sites statiques !\nDans mon exemple, j'utilise le générateur de site statique\nMetalsmith. C’est juste car je travaille avec au\nquotidien et qu'il est assez simple de faire tourner de nouveaux plugins dessus.\nMais ça marche aussi avec\nles générateurs de Jekyll par\nexemple. Du moment que votre générateur sait comment utiliser des fichiers JSON\npour les données en entrée, vous pouvez utiliser les exemples de code ci-dessous\npour stocker ce qui est retourné dans l’étape de préparation des données. C’est\nparti !\nL'API WordPress\nChaque installation de WordPress possède une API JSON à part entière. Ça veut\ndire que vous pouvez accéder aux articles et aux pages via des URLs. Ça m'a tout\nl’air d’un CMS headless ça ! Si vous avez une instance de WordPress qui tourne\nquelque part, ajoutez \/wp-json\/wp\/v2\/posts à la fin de l’URL principale. Vous\ndevriez avoir quelque chose en sortie !\nEn fait les 10 derniers articles ainsi que toutes leurs métadonnées vous sont\nprésentés dans un format JSON facile à comprendre.\nRécupérer les informations sur l’auteur\nVous remarquerez d’emblée que le champ author de chaque article est un nombre.\nC’est ainsi que les données sont structurées dans WordPress. Il faudrait que\nvous alliez chercher le numéro dans la table des auteurs et WordPress ne propose\npas une URL d’API pour cela.\nToutefois, vous pouvez activer l’option masquée par défaut _embed qui va\najouter toutes les données relatives à l’auteur.\nDonc avec https:\/\/url-vers-votre-blog\/wp-json\/wp\/v2\/posts?_embed vous avez\ntoutes les données dont vous avez besoin !\nRécupérer tous les articles\nSi vous avez un nombre très important d’articles, le prochain défi sera de les\nrécupérer tous. Malheureusement ce ne sera peut-être pas possible en une seule\nrequête. Vous pouvez maximiser le nombre d’articles retournés à 100 en ajoutant\nle paramètre supplémentaire per_page :\nhttps:\/\/url-vers-votre-blog\/wp-json\/wp\/v2\/posts?_embed&amp;per_page=100\nEnsuite, il va falloir récupérer les informations de pagination. Il existe un\nparamètre page qui permet de sélectionner la page que vous souhaitez\nrécupérer. Vous avez la possibilité de l’utiliser récursivement pour récupérer\ntoutes les pages qui existent. Vous pouvez également vérifier les entêtes HTTP\npersonnalisés de WordPress pour connaître le nombre de pages à récupérer. Ici,\nc'est comme ça que je vais procéder. Gardez juste en tête que les paramètres\nCORS de votre serveur doivent autoriser le passage de ces entêtes à votre\nclient. L'entête personnalisé qui contient le nombre de pages est\nX-WP-TotalPages.\nPour télécharger les données, j'utilise\nisomorphic-fetch, qui fournit\nla même API fetch pour Node et pour le navigateur. Regardons tout ça :\nconst fetch = require(\"isomorphic-fetch\");\n\nconst mainURL = \"http:\/\/chemin-vers-votre-blog\";\nconst apiURL = \"\/wp-json\/wp\/v2\/posts\";\nconst url = `${mainURL}${apiURL}?_embed&amp;per_page=100`;\n\nfetch(url) \/* 1 *\/\n  .then((res) =&gt; {\n    const noPages = res.headers.get(\"X-WP-TotalPages\"); \/* 2 *\/\n    const pagesToFetch = new Array(noPages - 1)\n      .fill(0)\n      .map((el, id) =&gt; fetch(`${url}&amp;page=${id + 2}`)); \/* 3 *\/\n    return Promise.all([res, ...pagesToFetch]); \/* 4 *\/\n  })\n  .then((results) =&gt; Promise.all(results.map((el) =&gt; el.json()))) \/* 5 *\/\n  .then((pages) =&gt; [].concat(...pages)); \/* 6 *\/\n\nNous téléchargeons les 100 premiers articles de notre blog. Si notre blog\nWordPress contient moins de 100 articles, nous n'avons plus rien à\ntélécharger.\nL'entête X-WP-TotalPages nous indique combien il nous reste de pages à\ntélécharger.\nNous créons un tableau de promesses pour les pages à télécharger, nous\ncommençons à la page 2 (la page 1 a déjà été téléchargée)\nPromise.all nous permet de passer le premier résultat et tous les suivants\nissus de notre tableau pagesToFetch.\nAppel de promesse suivant : convertir tous les résultats en JSON.\nEt enfin nous convertissons tous nos résultats dans un seul et unique\ntableau qui contient toutes les données des articles de notre blog.\n\nL'appel .then suivant contiendra un tableau avec toutes les entrées du blog.\nVous pouvez stocker ces données sous forme de fichier JSON (si votre générateur\nde site n'est pas extensible) ou dans notre cas : créer une vraie page de\ndonnées que nous voulons générer.\nAjouter vos articles dans Metalsmith\nMetalsmith — comme beaucoup de générateurs de sites statiques — sait quel est le\ndossier qui contient vos fichiers source. La plupart du temps au format\nMarkdown. Ces fichiers sont ensuite convertis en HTML. Toutefois, Metalsmith\npermet aussi d’ajouter des données externes. Il est assez simple de manipuler\nles tableaux de fichiers et d’ajouter de nouveaux fichiers. La seule chose à\nsavoir c'est que chaque fichier doit posséder une clef unique : l’URL ou le\nchemin où il va être stocké. Le contenu de chaque entrée est un objet qui\ncontient toutes les données que vous souhaitez stocker. Regardons tout ça !\nUn plugin WordPress pour Metalsmith\nMetalsmith fonctionne avec des plugins. À chaque fois que vous lancez une\ngénération avec Metalsmith, il va appliquer tous les plugins que vous avez\ndéfinis, un peu comme avec Gulp.\nRéutilisons l’exemple de code précédent et améliorons-le pour en faire un plugin\nMetalsmith :\nconst { URL } = require('url’);\n\nconst wordpress = (url) =&gt; (files, smith, done) =&gt; { \/* 1 *\/\n  fetch(url)\n    \/* … include code from above …*\/\n    .then(allPages =&gt; {\n      allPages.forEach(page =&gt; {\n        const relativeURL\n          = new URL(page.link).pathname;             \/* 2 *\/\n        const key = `.\/${relativeURL}\/index.html`;\n        let value = page;                            \/* 3 *\/\n        value.layout = 'post.hbs';\n        value.contents =\n          new Buffer(page.content.rendered, 'utf8');\n        files[key] = value;                          \/* 4 *\/\n      });\n      done();                                        \/* 5 *\/\n    });\n}\n\nL'interface pour les plugins Metalsmith est (files, metalsmith, done). Le\npremier paramètre désigne l’ensemble des fichiers qui doivent être\ntransformés en HTML. Le deuxième paramètre est l’objet Metalsmith. Le\ntroisième paramètre est une fonction de callback. C’est particulièrement\nutile pour les opérations asynchrones. Appelez done lorsque votre plugin a\nfini son travail.\nUne fois que nous avons tous les articles à partir des appels à l’API (voir\nci-dessus), nous avons transformé quelque peu les données. D'abord, nous\ndevons modifier les permaliens de WordPress pour que Metalsmith puisse s'y\nretrouver. Nous utilisons le package URL de Node pour récupérer l’URL\nrelative (sans le nom de domaine) et à partir de cela nous créons un chemin\nrelatif dans le système de fichier. Vous remarquerez que nous ajoutons\nindex.html. De cette manière nous créons tout un tas de dossiers avec un\nseul fichier HTML dedans. Nous obtenons ainsi de belles URLs pour nos sites\nstatiques.\nEnsuite, nous créons des paires clé-valeur pour l’objet fichier. Chaque\nvaleur correspond à une entrée dans le tableau post que nous avons\nrécupéré plus tôt. Nous précisons ensuite le gabarit à utiliser et indiquons\nle contenu (le plugin metalsmith-layouts a besoin de ces deux valeurs pour\nfonctionner).\nAprès ça, nous stockons cette valeur dans le chemin relatif que nous avons\ndéfini plus tôt.\nUne fois qu'on a fait ça pour tous les articles, nous appelons la fonction\nde callback done pour indiquer la fin du traitement par nos plugins.\n\nParfait. En quelques lignes de code nous avons dit à Metalsmith d’étendre les\nfichiers qu'il transforme déjà avec les fichiers que nous récupérons à partir\nd’une API. C’est ce qui rend Metalsmith extrêmement puissant, car vous n'êtes\nplus lié à un seul et unique CMS. Vous pouvez même vous brancher sur différents\nsystèmes de gestion de contenu, récents ou plus anciens, et ne produire qu'un\nseul fichier en sortie. Trop bien !\nScript de génération pour Metalsmith\nNous voulons pouvoir utiliser notre nouveau plugin de manière très simple lors\nde l’enchaînement des traitements par Metalsmith. Nous ne faisons appel qu'au\nplugin layouts qui va générer un contenu un peu plus sémantique à partir de\nnos fichiers Handlebars.\nconst Metalsmith = require('metalsmith');\nconst layouts = require('metalsmith-layouts');\n\n\/** le plugin  **\/\n\nMetalsmith('.')\n  .use(wordpress(apiURL))\n  .use(layouts({\n    engine: 'handlebars'\n  }))\n  .source('.\/source')\n  .destination('.\/build’)\n  .build((err) =&gt; {\n    if (err) throw err;\n    console.log('Finished’);\n  });\nOn commence d’abord par récupérer toutes les données depuis l’API WordPress,\npuis on les fait passer dans le plugin metalsmith-layouts. Puis on lance la\ngénération à proprement parlé. Si vous exécutez ce fichier, vous verrez qu'il\ngénère un dossier build dans votre système de fichier.\nGabarit de page\nLe fichier de gabarit est un fichier Handlebars qui définit une structure HTML\nde base. contents fait référence au champ que nous avons défini plus tôt dans\nnotre plugin Metalsmith pour WordPress. Le reste vient directement de l’objet et\nintègre automatiquement les données de _embedded l’auteur. C’est tout simple :\n&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;meta charset=\"UTF-8\"&gt;\n  &lt;title&gt;{{title.rendered}}&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;h1&gt;{{title.rendered}}&lt;\/h1&gt;\n  {{{contents}}}\n\n  &lt;aside&gt;\n    by {{_embedded.author.0.name}}\n  &lt;\/aside&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\nÉtape suivante\nExcellent ! Après m'être familiarisé avec l’API de WordPress, avoir récupéré\ntous les contenus, créer des sites statiques à partir des données a été un jeu\nd’enfant. J'ai créé un\ndépôt à valeur d’exemple sur GitHub.\nDites-moi ce que vous en pensez.\nL'étape suivante serait de créer un petit plugin WordPress (un vrai avec du PHP\net tout) qui utilise le hook de publication pour activer automatiquement votre\nsystème d’intégration continue. Mais vu la richesse de l’écosystème de\nWordPress, il se pourrait bien de quelque chose de ce genre existe déjà.\nUn commentaire ? Envoyez un tweet !",
      "content_html": "<aside class=\"note note-intro\"><p>La mode est au <a href=\"/2017/12/15/cms-headless/\">CMS headless</a>,\ncomprenez au découplage du back et du front. WordPress n'échappe pas à la règle.\nMême si son API REST ne permet pas encore de faire tout ce qu'on voudrait, il\nest tout à fait possible d’aller récupérer les contenus entrés par les\nutilisateurs dans l’interface d’administration qu'ils affectionnent tant pour\nensuite les passer à la moulinette d’un générateur de site statique. Stefan\nBaumgartner a testé pour vous avec <a href=\"http://www.metalsmith.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Metalsmith</a>,\nvoici comment il a procédé.</p></aside>\n<p>La toute-puissante <a href=\"/2017/03/16/5-raisons-de-tester-la-jamstack/\">JAMStack</a> offre des sites web statiques rapides\net 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\narriver que de temps à autre, vous vous retrouviez devant un blog WordPress avec\ntellement d’articles (et d’auteurs qui ont peur du changement !) pour que la\nraison vous pousse à ne pas le migrer. Mais WordPress aussi fonctionne en\nheadless. D'ailleurs, le propre service d’hébergement de WordPress communique\navec le core de WordPress uniquement au travers de son API, l’interface\nd’édition fait partie de la toute nouvelle et belle application\n<a href=\"https://developer.wordpress.com/calypso/\" target=\"_blank\" rel=\"noopener noreferrer\">Calypso</a>.</p>\n<p>Un des gros avantages quand on utilise un générateur de site statique, c'est que\ngénéralement il se moque de la provenance de votre contenu. Utilisons donc\n<a href=\"http://v2.wp-api.org/\" target=\"_blank\" rel=\"noopener noreferrer\">l’attrayante API REST de WordPress</a> pour récupérer un\npeu de contenu et générer des sites statiques !</p>\n<p>Dans mon exemple, j'utilise le générateur de site statique\n<a href=\"http://www.metalsmith.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Metalsmith</a>. C’est juste car je travaille avec au\nquotidien et qu'il est assez simple de faire tourner de nouveaux plugins dessus.\nMais ça marche aussi avec\n<a href=\"https://jekyllrb.com/docs/plugins/#generators\" target=\"_blank\" rel=\"noopener noreferrer\">les générateurs de Jekyll</a> par\nexemple. Du moment que votre générateur sait comment utiliser des fichiers JSON\npour les données en entrée, vous pouvez utiliser les exemples de code ci-dessous\npour stocker ce qui est retourné dans l’étape de préparation des données. C’est\nparti !</p>\n<h2 id=\"l-api-wordpress\">L'API WordPress</h2>\n<p>Chaque installation de WordPress possède une API JSON à part entière. Ça veut\ndire que vous pouvez accéder aux articles et aux pages via des URLs. Ça m'a tout\nl’air d’un CMS headless ça ! Si vous avez une instance de WordPress qui tourne\nquelque part, ajoutez <code>/wp-json/wp/v2/posts</code> à la fin de l’URL principale. Vous\ndevriez avoir quelque chose en sortie !</p>\n<p>En fait les 10 derniers articles ainsi que toutes leurs métadonnées vous sont\nprésentés dans un format JSON facile à comprendre.</p>\n<h3 id=\"recuperer-les-informations-sur-l-auteur\">Récupérer les informations sur l’auteur</h3>\n<p>Vous remarquerez d’emblée que le champ <code>author</code> de chaque article est un nombre.\nC’est ainsi que les données sont structurées dans WordPress. Il faudrait que\nvous alliez chercher le numéro dans la table des auteurs et WordPress ne propose\npas une URL d’API pour cela.</p>\n<p>Toutefois, vous pouvez activer l’option masquée par défaut <code>_embed</code> qui va\najouter toutes les données relatives à l’auteur.</p>\n<p>Donc avec <code>https://url-vers-votre-blog/wp-json/wp/v2/posts?_embed</code> vous avez\ntoutes les données dont vous avez besoin !</p>\n<h3 id=\"recuperer-tous-les-articles\">Récupérer tous les articles</h3>\n<p>Si vous avez un nombre très important d’articles, le prochain défi sera de les\nrécupérer tous. Malheureusement ce ne sera peut-être pas possible en une seule\nrequête. Vous pouvez maximiser le nombre d’articles retournés à 100 en ajoutant\nle paramètre supplémentaire <code>per_page</code> :</p>\n<p><code>https://url-vers-votre-blog/wp-json/wp/v2/posts?_embed&amp;per_page=100</code></p>\n<p>Ensuite, il va falloir récupérer les informations de pagination. Il existe un\nparamètre <code>page</code> qui permet de sélectionner la page que vous souhaitez\nrécupérer. Vous avez la possibilité de l’utiliser récursivement pour récupérer\ntoutes les pages qui existent. Vous pouvez également vérifier les entêtes HTTP\npersonnalisés de WordPress pour connaître le nombre de pages à récupérer. Ici,\nc'est comme ça que je vais procéder. Gardez juste en tête que les paramètres\nCORS de votre serveur doivent autoriser le passage de ces entêtes à votre\nclient. L'entête personnalisé qui contient le nombre de pages est\n<code>X-WP-TotalPages</code>.</p>\n<p>Pour télécharger les données, j'utilise\n<a href=\"https://www.npmjs.com/package/isomorphic-fetch\" target=\"_blank\" rel=\"noopener noreferrer\">isomorphic-fetch</a>, qui fournit\nla même API <code>fetch</code> pour Node et pour le navigateur. Regardons tout ça :</p>\n<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>);\n\n<span class=\"hljs-keyword\">const</span> mainURL = <span class=\"hljs-string\">\"http://chemin-vers-votre-blog\"</span>;\n<span class=\"hljs-keyword\">const</span> apiURL = <span class=\"hljs-string\">\"/wp-json/wp/v2/posts\"</span>;\n<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>;\n\nfetch(url) <span class=\"hljs-comment\">/* 1 */</span>\n  .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">res</span>) =&gt;</span> {\n    <span class=\"hljs-keyword\">const</span> noPages = res.headers.get(<span class=\"hljs-string\">\"X-WP-TotalPages\"</span>); <span class=\"hljs-comment\">/* 2 */</span>\n    <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>)\n      .fill(<span class=\"hljs-number\">0</span>)\n      .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>\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-built_in\">Promise</span>.all([res, ...pagesToFetch]); <span class=\"hljs-comment\">/* 4 */</span>\n  })\n  .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>\n  .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">pages</span>) =&gt;</span> [].concat(...pages)); <span class=\"hljs-comment\">/* 6 */</span></code></pre>\n<ol>\n<li>Nous téléchargeons les 100 premiers articles de notre blog. Si notre blog\nWordPress contient moins de 100 articles, nous n'avons plus rien à\ntélécharger.</li>\n<li>L'entête <code>X-WP-TotalPages</code> nous indique combien il nous reste de pages à\ntélécharger.</li>\n<li>Nous créons un tableau de promesses pour les pages à télécharger, nous\ncommençons à la page 2 (la page 1 a déjà été téléchargée)</li>\n<li><code>Promise.all</code> nous permet de passer le premier résultat et tous les suivants\nissus de notre tableau <code>pagesToFetch</code>.</li>\n<li>Appel de promesse suivant : convertir tous les résultats en JSON.</li>\n<li>Et enfin nous convertissons tous nos résultats dans un seul et unique\ntableau qui contient toutes les données des articles de notre blog.</li>\n</ol>\n<p>L'appel <code>.then</code> suivant contiendra un tableau avec toutes les entrées du blog.\nVous pouvez stocker ces données sous forme de fichier JSON (si votre générateur\nde site n'est pas extensible) ou dans notre cas : créer une vraie page de\ndonnées que nous voulons générer.</p>\n<h2 id=\"ajouter-vos-articles-dans-metalsmith\">Ajouter vos articles dans Metalsmith</h2>\n<p>Metalsmith — comme beaucoup de générateurs de sites statiques — sait quel est le\ndossier qui contient vos fichiers source. La plupart du temps au format\nMarkdown. Ces fichiers sont ensuite convertis en HTML. Toutefois, Metalsmith\npermet aussi d’ajouter des données externes. Il est assez simple de manipuler\nles tableaux de fichiers et d’ajouter de nouveaux fichiers. La seule chose à\nsavoir c'est que chaque fichier doit posséder une clef unique : l’URL ou le\nchemin où il va être stocké. Le contenu de chaque entrée est un objet qui\ncontient toutes les données que vous souhaitez stocker. Regardons tout ça !</p>\n<h3 id=\"un-plugin-wordpress-pour-metalsmith\">Un plugin WordPress pour Metalsmith</h3>\n<p>Metalsmith fonctionne avec des plugins. À chaque fois que vous lancez une\ngénération avec Metalsmith, il va appliquer tous les plugins que vous avez\ndéfinis, un peu comme avec Gulp.</p>\n<p>Réutilisons l’exemple de code précédent et améliorons-le pour en faire un plugin\nMetalsmith :</p>\n<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’);\n\nconst wordpress = (url) =&gt; (files, smith, done) =&gt; { /* 1 */\n  fetch(url)\n    /* … include code from above …*/\n    .then(allPages =&gt; {\n      allPages.forEach(page =&gt; {\n        const relativeURL\n          = new URL(page.link).pathname;             /* 2 */\n        const key = `./${relativeURL}/index.html`;\n        let value = page;                            /* 3 */\n        value.layout = '</span>post.hbs<span class=\"hljs-string\">';\n        value.contents =\n          new Buffer(page.content.rendered, '</span>utf8<span class=\"hljs-string\">');\n        files[key] = value;                          /* 4 */\n      });\n      done();                                        /* 5 */\n    });\n}</span></code></pre>\n<ol>\n<li>L'interface pour les plugins Metalsmith est <code>(files, metalsmith, done)</code>. Le\npremier paramètre désigne l’ensemble des fichiers qui doivent être\ntransformés en HTML. Le deuxième paramètre est l’objet Metalsmith. Le\ntroisième paramètre est une fonction de callback. C’est particulièrement\nutile pour les opérations asynchrones. Appelez <code>done</code> lorsque votre plugin a\nfini son travail.</li>\n<li>Une fois que nous avons tous les articles à partir des appels à l’API (voir\nci-dessus), nous avons transformé quelque peu les données. D'abord, nous\ndevons modifier les permaliens de WordPress pour que Metalsmith puisse s'y\nretrouver. Nous utilisons le package <code>URL</code> de Node pour récupérer l’URL\nrelative (sans le nom de domaine) et à partir de cela nous créons un chemin\nrelatif dans le système de fichier. Vous remarquerez que nous ajoutons\n<code>index.html</code>. De cette manière nous créons tout un tas de dossiers avec un\nseul fichier HTML dedans. Nous obtenons ainsi de belles URLs pour nos sites\nstatiques.</li>\n<li>Ensuite, nous créons des paires clé-valeur pour l’objet fichier. Chaque\nvaleur correspond à une entrée dans le tableau <code>post</code> que nous avons\nrécupéré plus tôt. Nous précisons ensuite le gabarit à utiliser et indiquons\nle contenu (le plugin <code>metalsmith-layouts</code> a besoin de ces deux valeurs pour\nfonctionner).</li>\n<li>Après ça, nous stockons cette valeur dans le chemin relatif que nous avons\ndéfini plus tôt.</li>\n<li>Une fois qu'on a fait ça pour tous les articles, nous appelons la fonction\nde callback <code>done</code> pour indiquer la fin du traitement par nos plugins.</li>\n</ol>\n<p>Parfait. En quelques lignes de code nous avons dit à Metalsmith d’étendre les\nfichiers qu'il transforme déjà avec les fichiers que nous récupérons à partir\nd’une API. C’est ce qui rend Metalsmith extrêmement puissant, car vous n'êtes\nplus lié à un seul et unique CMS. Vous pouvez même vous brancher sur différents\nsystèmes de gestion de contenu, récents ou plus anciens, et ne produire qu'un\nseul fichier en sortie. Trop bien !</p>\n<h3 id=\"script-de-generation-pour-metalsmith\">Script de génération pour Metalsmith</h3>\n<p>Nous voulons pouvoir utiliser notre nouveau plugin de manière très simple lors\nde l’enchaînement des traitements par Metalsmith. Nous ne faisons appel qu'au\nplugin <em>layouts</em> qui va générer un contenu un peu plus sémantique à partir de\nnos fichiers Handlebars.</p>\n<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>);\n<span class=\"hljs-keyword\">const</span> layouts = <span class=\"hljs-built_in\">require</span>(<span class=\"hljs-string\">'metalsmith-layouts'</span>);\n\n<span class=\"hljs-comment\">/** le plugin  **/</span>\n\nMetalsmith(<span class=\"hljs-string\">'.'</span>)\n  .use(wordpress(apiURL))\n  .use(layouts({\n    <span class=\"hljs-attr\">engine</span>: <span class=\"hljs-string\">'handlebars'</span>\n  }))\n  .source(<span class=\"hljs-string\">'./source'</span>)\n  .destination(<span class=\"hljs-string\">'./build’)\n  .build((err) =&gt; {\n    if (err) throw err;\n    console.log('</span>Finished’);\n  });</code></pre>\n<p>On commence d’abord par récupérer toutes les données depuis l’API WordPress,\npuis on les fait passer dans le plugin <code>metalsmith-layouts</code>. Puis on lance la\ngénération à proprement parlé. Si vous exécutez ce fichier, vous verrez qu'il\ngénère un dossier <code>build</code> dans votre système de fichier.</p>\n<h3 id=\"gabarit-de-page\">Gabarit de page</h3>\n<p>Le fichier de gabarit est un fichier Handlebars qui définit une structure HTML\nde base. <code>contents</code> fait référence au champ que nous avons défini plus tôt dans\nnotre plugin Metalsmith pour WordPress. Le reste vient directement de l’objet et\nintègre automatiquement les données de <code>_embedded</code> l’auteur. C’est tout simple :</p>\n<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>\n<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>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span>\n  <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>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span>\n  <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>\n  </span><span class=\"hljs-template-variable\">{{{contents}}}</span><span class=\"xml\">\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">aside</span>&gt;</span>\n    by </span><span class=\"hljs-template-variable\">{{_embedded.author.0.name}}</span><span class=\"xml\">\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">aside</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></span></code></pre>\n<h2 id=\"etape-suivante\">Étape suivante</h2>\n<p>Excellent ! Après m'être familiarisé avec l’API de WordPress, avoir récupéré\ntous les contenus, créer des sites statiques à partir des données a été un jeu\nd’enfant. J'ai créé un\n<a href=\"https://github.com/ddprrt/metalsmith-wordpress-sample\" target=\"_blank\" rel=\"noopener noreferrer\">dépôt à valeur d’exemple sur GitHub</a>.\nDites-moi ce que vous en pensez.</p>\n<p>L'étape suivante serait de créer un petit plugin WordPress (un vrai avec du PHP\net tout) qui utilise le hook de publication pour activer automatiquement votre\nsystème d’intégration continue. Mais vu la richesse de l’écosystème de\nWordPress, il se pourrait bien de quelque chose de ce genre existe déjà.</p>\n<p>Un commentaire ? <a href=\"https://twitter.com/ddprrt\" target=\"_blank\" rel=\"noopener noreferrer\">Envoyez un tweet</a> !</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2018/01/07/netlify-en-10-fonctionnalites/",
      "url": "https://jamstatic.fr/2018/01/07/netlify-en-10-fonctionnalites/",
      "title": "Netlify en 10 fonctionnalités",
      "summary": "Déployez et hébergez gratuitement vos sites statiques comme les pros : CDN, HTTPS, CI, environnements de build, formulaires, tests A\/B, et bien plus.",
      "date_published": "2018-01-07T20:05:43+00:00","content_text": "En l’espace de quelques années Netlify 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.\nNetlify — la contraction de Net et Simplify — 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, le passage de Smashing Magazine à une architecture Jamstack hébergée par Netlify a montré que ça pouvait aller bien au delà en faisant appel à différentes APIs et microservices.\nPhil Hawksworth, nouvellement en charge des relations avec les développeurs chez Netlify a publié une liste de fonctionnalités disponibles quelle que soit la formule utilisée, même celle entièrement gratuite.\n\n\n\n\n\nMais d’abord, comment démarrer simplement\nSi 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.\nLa manière la plus simple d’héberger un site chez Netlify est de glisser-déposer un dossier contenant vos fichiers dans un navigateur sur https:\/\/app.netlify.com.\n\n\n\n\nNetlify’s easy peasy drag and drop deployment\n\nVous pouvez aussi déployer directement grâce à l’utilitaire en ligne de commande, mais je prefère vous renvoyer à la documentation 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.\n1. Déploiements atomiques avec publication et retour en arrière immédiats\nSi 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é.\nChaque 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.\nLes 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.\nEn fait, tout ce que vous pouvez faire dans l’interface d’administration, vous pouvez le faire aussi avec l’API. La documentation de l’API 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 !\n2. Notifications et permaliens\nUne fois encore, il y a plus d’une fonctionnalité dans cet élément de ma liste, il faudra vous y faire.\nNetlify 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).\nVous 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.\nCe 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.\nAvoir 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.\"\nEt cet accès instantané vous est partagé directement à chaque notification.\n3. Branches de déploiement et sous-domaines\nC’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.\nNetlify 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.\n\n\n\n\n\n\nParamètres du déploiement continu.\n\nUne 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 :\nma-branche--mon-site.netlify.com\nGrâce à la gestion des DNS de Netlify, 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.\n4. Tests A\/B, Tests A\/B avec plusieurs variantes ou tests séparés\nIl existe plusieurs variantes et termes pour désigner les tests A\/B, Netlify appelle cela le split testing parce que c'est ce que ça fait : découper le trafic de votre site entre les différentes branches de votre choix.\nVous 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.\n\n\n\n\n\n\nLa configuration du split testing chez Netlify.\n\nCette 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.\nVous 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.\nVu 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.\nDe 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.\nL'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.\nChaque variante de test est servie et rendue comme sur la \"production\". Fantastique.\n5. Commandes de génération contextuelles\nNon 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.\nIl 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 staging.votre-projet.com et testing.votre-projet.com et tout ce que vous voulez à côté de votre www.votre-projet.com 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.\nVous 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.\nCela vous permet de faire des choses comme générer la production avec npm run build:prod et une branche de fonctionnalité avec npm run build:ma-fonctionnalite. Pratique !\nCela se paramètre à l’aide d’un fichier de configuration netlify.toml qu'on peut laisser à la racine du projet pour accéder à toutes sortes d’options pour vos déploiements sur Netlify.\nPar exemple :\n\n\nExemple de fichier de configuration de site netlify.toml.\n\nVous trouverez plus d’informations à ce sujet dans la documentation des contextes de déploiement.\n6. Gestion des certificats SSL et SSL gratuit avec Let’s Encrypt\nMê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.\nUn des créateurs de Netlify explique tout ça très bien en donnant cinq bonne raisons de servir votre site en HTTPS et Google a également publié de bons articles qui expliquent pourquoi HTTPS est si important, quelle que soit l’architecture utilisée.\nVous ê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.\nNetlify rend triviale la configuration de HTTPS sur vos noms de domaines.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.\nLa plupart des gens peuvent se contenter de la gestion automatisée grâce aux certificats offerts par Let's Encrypt. 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.\n\n\n\n\n\n\nLa configuration de SSL chez Netlify avec renouvellement automatique des certificats grâce à Let’s Encrypt.\n\n7. Lancer des tests avec l’intégration continue de Netlify\nUne 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 build lancé dans votre environnement de développement ou sur un serveur d’intégration continue peut en fait être directement exécuté sur Netlify.\nSi 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, vous serez prévenus de l’issue finale.\nRemplacer 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.\n8. Gestion des formulaires\nSi 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.\nSi 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, Netlify va exposer le point d’accès qui va bien pour le formulaire et rendre toutes les données postées accessibles pour vous depuis l’interface d’administration et l’API.\nComme 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.\nLes soumissions de formulaires peuvent aussi déclencher des notifications. Tout devient alors possible : des messages Slack, des webhooks ou même des intégrations Zapier.\n9. Redirections, réécritures et proxy\nN'oubliez aucune URL en route ! En ajoutant un fichier _redirects 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.\nVous avez aussi la possibilité de préciser le code de réponse HTTP dans le fichier _redirects, ce qui vous permet de personnaliser vos erreurs 404 ou même de rendre d’autres ressources accessibles au travers d’un proxy.\nVoici un exemple :\n\n\nUn exemple de fichier de configuration _redirects.\n\nVous voulez des splats, des placeholders, des paramètres de requêtes et plus encore ? Jetez un œil à la documentation sur les redirections.\n10. Contrôle des entêtes personnalisés\nCelui là ravira toux ceux qui ont hébergé leur site sur GitHub Pages et qui ont couru après le score parfait sur Lighthouse ou Page Speed Insights. 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é.\nMaintenant vous l’avez.\nNetlify utilise pour cela une approche similaire à celle de la gestion des redirections que nous venons de voir plus haut. Grâce à un fichier _headers déposé dans votre dossier de déploiement, vous pouvez ainsi contrôler les entêtes HTTP de toutes les ressources de votre site.\nEt vous pouvez faire bien plus que contrôler les entêtes de cache. La possibilité de configurer vos entêtes à l’aide de fichier _headers vous permet de définir votre politique de sécurité en matière de contenu (CSP), vos options X-Frame et plein d’autres choses toutes aussi importantes pour vous aider à contrôler la sécurité de votre site.\n\n\nUn exemple de fichier de configuration _headers.\n\nBé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.\nÇa en fait 10, mais ce n'est pas tout\nJ'aurais pu mentionner bien d’autres choses, mais vous ne voulez pas d’une liste interminable.\nPlus 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.\nÇa vaut le coup de garder un œil sur Netlify, d’autres fonctionnalités prometteuses sont actuellement testées par des alpha-testeurs enthousiastes.\nVous pouvez suivre Netlify sur Twitter pour rester informé des nouvelles fonctionnalités, plonger dans la documentation, ou prendre connaissance des nos projets open source pour étendre les possibilités de l’écosystème Jamstack.",
      "content_html": "<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>\nNetlify — 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>\n<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>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<h2 id=\"mais-d-abord-comment-demarrer-simplement\">Mais d’abord, comment démarrer simplement</h2>\n<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>\n<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>\n<p><figure>\n<div title=\"Netlify’s easy peasy drag and drop deployment\" style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div>\n<figcaption>Netlify’s easy peasy drag and drop deployment</figcaption>\n</figure></p>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"2-notifications-et-permaliens\">2. Notifications et permaliens</h2>\n<p>Une fois encore, il y a plus d’une fonctionnalité dans cet élément de ma liste, il faudra vous y faire.</p>\n<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>\n<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>\n<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>\n<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>\n<p>Et cet accès instantané vous est partagé directement à chaque notification.</p>\n<h3 id=\"3-branches-de-deploiement-et-sous-domaines\">3. Branches de déploiement et sous-domaines</h3>\n<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>\n<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>\n<figure>\n<picture title=\"Paramètres du déploiement continu.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Paramètres du déploiement continu.</figcaption>\n</figure>\n<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>\n<p><code>ma-branche--mon-site.netlify.com</code></p>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"La configuration du split testing chez Netlify.\">\n<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\">\n<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\">\n<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,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAMlElEQVR4nMVbWYLkKA59AmxnZGX1meYM8zP3P0PXMtUZRvOBACFkhyMru0dVBF4wix5PEthJ//r3f3iJEeuy4Pay4fX2ircvr/j69gVvX17xervh9XbDy7ZhXVesKSGlhBgCQgggIqD8hzpAPWD1S/1mK1evEckZEUKoeUCgckyEkg9tFGFmMAMMBoZjyUshHAlduqPH08fFrXoGg8HM2POOfd/xfn/H+1/v+PXXL/z8+Qvff/7E9+8/8O37d/z57Rv+/P4DP37+wM///sKvv97xnnekQGXQQRQcAomiSwoURCE9oaWKwTEo1LXtDJ7aLdvGmDoYFbhZizzUTgCYzCTwHjH94oP77sNc+yFtczmWnqrJRKAg56Em0bXoOARC5IC0pIgUVUoJMcaSQkCIATH01EEjBVZXkssUsnPNgEY4UL4DBtHYBjOYCMyMMmPHmXwqJ4UGQnHnea2dBQwWYJiEmRzkEUbgqreiyxgDUgyiZ9F1Ksd7TgDtSNu6IoaAdV1KWhLWpeTLkrBUE1VBirE1UE2LVdL58XyvKdoBYAZjBpibwnhQ5ENQzgqwus2siiozpc2i5JyzTFAGc0SOGVEBkJLoVXT8vqzIewYAxD0i3bYNMUYsS8K2bpJWAWXBklJLlUUxFUCKWStKA3o+mKITQKRkOX0ACMgyRAgygMLVagy+61lA2NwbQWZ1rbbX8xwIlDtLmRk5Z+x7EgAWvC8rtvWO+/sd+54BMEKI2POO9Pp6QwwBy7LgZVtxe9nw8rJh29bixNdFEDVsCaHYwqoglQ+KdgbcgTNgnjKksnGs2zpvVuZlnNnXAan3eDxtFz3mlMCCEZiQSemFMzgn5Lxg3zPua3H4+74j7xnMQKCAJb5jzxnpy+srYgxY0oJtW3HbNtxeNty2F7ysq7BlxZIKMEOEJVEWYWSHBmICpAEGBSIGFlgQNHs0+7q2GhzjzFXnVwGxTt0WmUzi0DaDc0AOGbTvfezMYM7IWadSnoiQYsT7/R37npG+vn1BDAEpJWzripetMOS2bXjZivna1gVLWpCWzo4gDAlNkdog+dQYHLuyaX3Gjyxwo63B3HWliEq6ApqShjuHQBzdttEyqwusDmrInQMj5DqxdBDAYr641UFEiCFiSQn3+70w5I+3NwQBZF2WAsC2FnZshR3rkrCkpfiOGHuITB0MG10dBZuDWRtmvM5HAAZzptqhQUEjG45M2aHm59Mn2FIjPCBw7iar6YX7pOGuh0Al4lrXFff7HTlnpD++fi0xcCxIrUuNtjoziqmqYMQW+vZGKwo66h9ZMoa9aCx4lFvGuOSTRVk51IAwGNRC46tyxpBGQnOxxBQlHCbKdqk2CBEQQpBgasH7+734FM5IX9/eCiAhtNBsSRL2ihNPEmGF2MEoi54wK9I0rM7U7xk7ruRWSSMgDAYxwEQgAYNOVupG1+M4aGbKfKjCXmYQj5O1+UsVTcZQItZl2Rs7MjPS17dX1G2KGCRejjVmjkixrkGqmarsCPNCTSlaK7+fjJAdKdsy5RSQqnCwMkviXZjtDDgVXUKDQGwL2ZJ1YUrNV4w+sAYqACE0dqSUcL9L1MUZzFyirI5aaE47ypojKCc+mSoSQg5mRCndXMfB9ceAjPdrbQyUCdGceDEXrMNdpkNFn4l+ik+xLD0hIomaUMyWsJQABKCBEkJAuJfJf4879mWMutLt9tLQLCzpbAkGiECy9kB3sp6iPJCcrA/drcf4IBVWV0fZQOmqa1snHccxKpoI5ir47Iq2CAzdCVZgcADAZVnAMnkD5bZvmPeIlDP2nMGZkWVhm27bJmangkLNLFUTVReAY1RlzYnt/IG9V6iclZ380sQOUQtzmX6DtrmfMkEbsl7CU7Z/Zbzt2ILQKyQIo5gQuSiale8gCiUSixkxMzIXQGqEmNZ1baaHCMMOpQbD3eAbOqf7PJx52aSExpITnQxrnRY/iv0e6KLN5swBy5Rq7s5l9JXTeBxzygQEBsABTIxAhBwYzKGZqMzd9zEz0rosg4IJmAAYNhC9NccRMMbswCt1VKSV8ucwF2M9tqWmqedDLCtmlpyA4o1RTyJA90Cdl18mgImLv2NGCOa9jYwlxZQmMHofZhBI9+ZAmZYhZ7N+7D4uldWr83aNR/VahnisGIE5nhanzHjQ93qtLFNoCI/7MPoOckoxmofJP5bzqSMHpuiYHaa0LcaYlH0kHjC1dm4H4xjYlhl643T4AjNs23N7pHzLvIugaZVCCH6lNKnebf2Rzr3ZNfRh2JK4GpT2gbA6H5+ubc5XfdPlTqmp72cgTI4EaPFGC8gIIKb+stFICkbx8xroAjCnQk0LZzuvPP5cEr2PVc6953uPj2r2FfocM85qbqaszQT2Yg0AQKoLGqdLRaytftSFeSk9P6TpijMwvJnvyPi+tZurp6Qbm6eYYcfL3mrHNCFFvHGlUmeJWMZhHdf3jNh9IO+1Z7vN7lMP6r3OqGPxI8gPMeNki2YKKhz/l2yodvaW7WzoEzM8Zkl4dwTKc1x8tpwVzQi1Aal3lG1QY548VP6J6W31HuxAJ1XDcd22IxfEY0bdeKvfUfVwT384cN6fS3JiZqZiXmAFTNHmU9bBqXhghwDvMuSwUocprhk7nCVKtcKMzBYUnlhyocXDLtsi5J77z7v+4HETjg9pP6dPV1ZaMYCcREA4VoWNvXV4rZVvtwtq0gw568fUC6NxHwDnHNd0fwbSoU6OaAd0My5+wg17H3XKfYcMywxLaW7rO67sUBtph0y5JDQp+jogxlnjXOl9OE7fpuXA5I3nanSZgzYPAPHdq+fc+oSgVlhDaBWf+RiYU9FKVdc0FNaMkCk3L8aOgHAWk4/WY7/JjCqHDBFDIiax+4N5J9eYkGquuPejsmQCJmfDkmOhqlQ1/Yn7rGtNK2fJrZxdGT/2+m3J0Lb3+RKbvLtXmFHl3GSJ3Tm1l55PU2ftq3DxE7mxRBgi75ItIJO9l/caRGVVpWGwYLB5Tl6w+5WfDZ/1m0Cq67lz0Qp/ghlVXEDYpMaQOlOIJuXp8Q7scq5pxmQev1UaB6fBYFnA1msY/YEJTYf3KxhfO/f33H1OXY+vromZKpcjuOSGtqNH7jNO6KtnSl9c+1OAFTSsnhnTCF/X92hkCN1k6lcD9b67dqDqzD1A6iuHMVLUxzp/Wp5gRpXGEA+YI4boMoNd7ehMSn52qTcoDxhz+55Gs+EyKJjqtkHCM+uSk4E8VTzpWVtzHo70FxzsNMBtP436JXkbputTfXSSHoALhjoOTcFz3oFRBquj1t9+uoCoZxUT+9PPyUeeOQQkd4uFrvQewXh0nl+WHne0mQoqH1Po710fAdKV6YOiDd3whhPKt0gHfEDsZuMH5IPsShYIbWxyPW+FukEklMhFuc1yzTKl9k8lKIWGIKDXqOASQ/p9HwjpEZlz9A6cMuEzAFHjfkaStvSVEZkrSyRUFc1qj6DfGgwzvn1UwKozxbsVJYq7JiAglNZJI3cCCDmAWDAcJXqvk49ZhQGQppiPyrM+JNfFm/qXIeFoPZdIq0Vg8gSpf6HlGKKWbtvKIEMNmRUzxgjLOF+MZmz4Q0rjOxy9T6zpDD94xvMZn+Dcr9aQqgNvzKiLNzB2uZZR1g1ZytYmKjMCle+P6vdiASh/N6JKEtWVfikVhBnDa9c2u51oyKTQyhuNugxx1GN8jQemFh1tfkgugprakkOxY0cBZUdJFaAKSHEl3JgRQIhyRXkKiYZqhwTCIBZKPsHgjoXk2qY7gGA+P9KmrwLLkmsymLDfkEc1pCwHlSG7rJ7vAHbuKQPNhFXRzMjwl/2hqcw4VdLLywNnqgDorIEBQ55+miXPy2+zBHgIaouyqsIzUJghDLlDMUUSgPZld5Q0dZPQtpBGe90Bsn2cIh4vPwpV/wH5LJa0uhxJwxZG9RnCiLsAclcAVY4QOjMy+qqdqIMViPqEUrarOX01sS+BYXJ3UEphnwXUpzDDygGwAyBVsbtJGpTGEHRAZM1YIiwWU4YCbHHmPQCoYHTnXfv3AAAZhJv/zfKZzHDrVjLsZWlQ6szXoFQHX9kQ0U1SgPgTkme5MOS4J3SdHbr8VTC88PVJ+VuYYcXoaDRZGMHQgNSkzRMkzzZxXXj3fa5e2vhfc83NH8zOww8tflP+Tma4bcFhiGfCLFBVLBB2M3EAQ47n4Y3RlivupmYdyYNnP8CUf4QZVqSfSStQy1l3ju4NYNR25ITqQXU4Z0r+oHwWU/5JZlg5ZIgn3Rl/0DZXMqj31PZPl938St2fwJT/CzOM/A/eLogyCkIVHQAAAABJRU5ErkJggg==);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\">\n</picture>\n<figcaption>La configuration du split testing chez Netlify.</figcaption>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<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>\n<p>Chaque variante de test est servie et rendue comme sur la \"production\". Fantastique.</p>\n<h3 id=\"5-commandes-de-generation-contextuelles\">5. Commandes de génération contextuelles</h3>\n<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>\n<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>\n<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>\n<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>\n<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>\n<p>Par exemple :</p>\n<p><figure>\n<script src=\"https://gist.github.com/philhawksworth/61715131c5d229c06f161e82e93db803.js\" title=\"Exemple de fichier de configuration de site netlify.toml.\">\n<figcaption>Exemple de fichier de configuration de site netlify.toml.</figcaption>\n</figure></p>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"La configuration de SSL chez Netlify avec renouvellement automatique des certificats grâce à Let’s Encrypt.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>La configuration de SSL chez Netlify avec renouvellement automatique des certificats grâce à Let’s Encrypt.</figcaption>\n</figure>\n<h3 id=\"7-lancer-des-tests-avec-l-integration-continue-de-netlify\">7. Lancer des tests avec l’intégration continue de Netlify</h3>\n<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>\n<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>\n<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>\n<h3 id=\"8-gestion-des-formulaires\">8. Gestion des formulaires</h3>\n<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>\n<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>\n<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>\n<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>\n<h3 id=\"9-redirections-reecritures-et-proxy\">9. Redirections, réécritures et proxy</h3>\n<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>\n<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>\n<p>Voici un exemple :</p>\n<p><figure>\n<script src=\"https://gist.github.com/philhawksworth/7b12a0785266f8dcf1960d2df93dfc59.js\" title=\"Un exemple de fichier de configuration _redirects.\">\n<figcaption>Un exemple de fichier de configuration <code>_redirects</code>.</figcaption>\n</figure></p>\n<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>\n<h3 id=\"10-controle-des-entetes-personnalises\">10. Contrôle des entêtes personnalisés</h3>\n<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>\n<p>Maintenant vous l’avez.</p>\n<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>\n<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>\n<p><figure>\n<script src=\"https://gist.github.com/philhawksworth/d1deda75c8bc3d025e7d62639f904222.js\" title=\"Un exemple de fichier de configuration _headers.\">\n<figcaption>Un exemple de fichier de configuration <code>_headers</code>.</figcaption>\n</figure></p>\n<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>\n<h2 id=\"ca-en-fait-10-mais-ce-n-est-pas-tout\">Ça en fait 10, mais ce n'est pas tout</h2>\n<p>J'aurais pu mentionner bien d’autres choses, mais vous ne voulez pas d’une liste interminable.</p>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/12/15/cms-headless/",
      "url": "https://jamstatic.fr/2017/12/15/cms-headless/",
      "title": "C’est quoi un CMS headless ?",
      "summary": "Un CMS headless s'occupe uniquement de vous aider à modéliser et à saisir vos contenus pour les fournir au terminal de votre choix.",
      "date_published": "2017-12-15T15:40:50+00:00","content_text": "Dans les architectures Jamstack, 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 headless — 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.\nSi les architectures Jamstack sont très performantes, 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.\nL'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 APIs GraphQL. Tout le monde a donc à y gagner, les personnes en charge de la modélisation de contenu auront à leur disposition des outils beaucoup plus souples 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.\nLes 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 Algolia puissent au passage indexer votre contenu (exemple avec Kentico). 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. Gatsby intègre ainsi nativement la notion même de source afin de pouvoir se connecter à n'importe quelle source de données externe.\nLe 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.\n\n\n\n\n\n\nAperçu d’une architecture de CMS découplée\n\nSi 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.\nDistinguons les CMS headless qui vous permettent de modifier les fichiers de votre dépôt Git (Forestry, Siteleaf, Netlify CMS, etc.) de ceux qui mettent vos contenus à disposition via une API (Contentful, Prismic, Directus, GraphCMS, etc.).\nChris Macrae détaille la différence entre les deux approches dans \n\n.\nQuoi 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 headless, d’ici à parler de révolution, il n'y a qu'un pas. Tout se recoupe !\nSi 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.\nL'équipe de rédaction de Smashing Magazine édite principalement un site web et l’utilisation de Netlify CMS — 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.\nPasser 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.\nSi jamais cela peut vous rassurer, Contentful, un des acteurs majeurs du marché vient de lever 28 millions de dollars tout comme Algolia ou Netlify 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.\n\nIf software is eating the world, SaaS APIs are eating the development world.\n\nVoilà 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.\nQuelques articles en anglais pour approfondir le sujet :\n\nLe guide ultime du CMS headless de Kentico (PDF, Epub)\nLes bénéfices d’un CMS headless (Forbes)\nC’est quoi un CMS headless ? (CSS Tricks)\nCMS headless découplé (Contentful)\nheadless CMS en 5 minutes (StoryBlok)\nLivre blanc sur l’API REST de WordPress (PDF)\n\nListes de CMS headless :\n\nhttps:\/\/headlesscms.org\/\nTools: headless CMS (The New Dynamic)\n",
      "content_html": "<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>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"Aperçu d’une architecture de CMS découplée\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<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>\n</figure>\n<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>\n<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>\n<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;\">\n<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>\n</div>.</p>\n<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>\n<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>\n<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>\n<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>\n<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>\n<blockquote>\n<p>If software is eating the world, SaaS APIs are eating the development world.</p>\n</blockquote>\n<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>\n<p>Quelques articles en anglais pour approfondir le sujet :</p>\n<ul>\n<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>\n<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>\n<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>\n<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>\n<li><a href=\"https://www.storyblok.com/tp/headless-cms-explained\" target=\"_blank\" rel=\"noopener noreferrer\">headless CMS en 5 minutes (StoryBlok)</a></li>\n<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>\n</ul>\n<p>Listes de CMS headless :</p>\n<ul>\n<li><a href=\"https://headlesscms.org/\" target=\"_blank\" rel=\"noopener noreferrer\">https://headlesscms.org/</a></li>\n<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>\n</ul>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/12/13/site-ecommerce-statique-scalable-avec-hugo/",
      "url": "https://jamstatic.fr/2017/12/13/site-ecommerce-statique-scalable-avec-hugo/",
      "title": "Un site e-commerce statique et extensible avec Hugo",
      "summary": "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 ?",
      "date_published": "2017-12-13T12:10:36+00:00","content_text": "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 ?\n\n\n\n\n\nLes 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 !)\nMais 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 — https:\/\/www.smashingmagazine.com\/ est un des sites importants les plus en vue à être passé au statique.\nAvantages\nLes avantages des sites statiques sont clairement documentés, et s'il fallait résumer :\n\nPerformance : 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 CDN.\nMontée en charge : 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.\nSécurité : Tout est dans le code source des pages HTML.\n\nCe sont de très bonnes raisons d’utiliser un GSS pour des sites de contenu. Mais pour quelqu'un qui travaille exclusivement dans le e-commerce depuis 10 ans — cela semble être de formidables raisons d’utiliser un générateur de site statique pour une boutique en ligne.\nVous 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).\nPrise de conscience\nMaintenant, 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.\nMais 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.\nEt 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.\nSi un produit simple vient à manquer (un produit sans déclinaison de taille ou de couleur), le bouton ajouter au panier 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.\nLes fonctionnalités dynamiques\nEt 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 les recommandations personnalisées de produits. Algolia peut proposer une excellente expérience de recherche pour le Ecommerce. Les plateformes d’optimisation comme Optimizely fonctionnent sur le même principe, les sites statiques peuvent aisément intégrer toutes ces technologies.\nLe panier d’achat\nLes fonctionnalités de gestion du panier d’achat peuvent être également intégrées de la sorte. Les plates-formes headless comme moltin ou les paniers gérés en JS comme Snipcart 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’ERP, de WMS ou d’OMS — 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.\nEn résumé\nNous 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 ?\nPour y parvenir, notre architecture extensible ecommerce statique doit répondre à plusieurs critères :\n\nVitesse : 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.\nDonnées : 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.\n\nHugo\nLorsqu'il est question de vitesse de génération de site, on pense forcément à Hugo. 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 pages de produits très riches en contenu, 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.\nAfficher les informations d’un produit se fait très simplement :\n&lt;p&gt;\n   Ajustement : &lt;strong&gt;{{ .Params.fitting }}&lt;\/strong&gt; &lt;\/br&gt;\n   Finition : &lt;strong&gt;{{ .Params.last }}&lt;\/strong&gt; &lt;\/br&gt;\n   Taille (UK) : &lt;strong&gt;{{ .Params.uksize }}&lt;\/strong&gt; &lt;\/br&gt;\n   Semelle : &lt;strong&gt;{{ .Params.sole }}&lt;\/strong&gt;\n&lt;\/p&gt;\nAjouter 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 :\n{{ $f1path := (print \"shoefeatures\/\" $.Params.feature1 \".html\") }}\n{{ partial $f1path}}\n{{ $f2path := (print \"shoefeatures\/\" $.Params.feature2 \".html\") }}\n{{ partial $f2path}}\n{{ $f3path := (print \"shoefeatures\/\" $.Params.feature3 \".html\") }}\n{{ partial $f3path}}\n{{ $f4path := (print \"shoefeatures\/\" $.Params.feature4 \".html\") }}\n{{ partial $f4path}}\nTout 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.\nPerformance\nBon 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 front matter. 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.\nEn 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.\nQuand je lance la commande hugo 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 :\nBuilt site for language fr:\n0 draft content\n0 future content\n0 expired content\n1000 regular pages created\n8 other pages created\n0 non-page files copied\n50 paginator pages created\n0 tags created\n0 categories created\ntotal in 724 ms\nJe trouve cela très impressionnant. Il y a tout de même quelques points à prendre en compte :\n\nLa complexité : 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.\nLa gestion des assets : 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.\n\nMalgré 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.\nLes données\nMaintenant 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 systèmes de navigation hiérarchiques.\nEt 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.\nSi 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.\nIdé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.\nIci mes produits sont des chaussures. Dans les données front matter de chaque produit, j'ai entré les détails relatifs aux différentes variantes de taille de la façon suivante :\n\"variants\": [\n      {\n        \"title\": \"6\",\n        \"sku\": \"9000-6\"\n      },\n      {\n        \"title\": \"7\",\n        \"sku\": \"9000-7\"\n      },\n      {\n        \"title\": \"8\",\n        \"sku\": \"9000-8\"\n      },\n      {\n        \"title\": \"9\",\n        \"sku\": \"9000-9\"\n      }\n    ]\nLa 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 :\n{\n \"stock\": {\n    \"9000-1\": {\n     \"allocation\": 3\n    },\n    \"9000-2\": {\n     \"allocation\": 9\n    },\n    \"9000-3\": {\n     \"allocation\": 3\n    },\n    \"9000-4\": {\n     \"allocation\": 1\n    },\n    …\n  }\n}\nJ'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 data\/inventory\/gb.json (dans Hugo les fichiers de données sont censés être stockés dans le répertoire data).\nDans le gabarit de la page produit, je peux accéder à la valeur de la variable allocation de cette manière :\n&lt;ul&gt;\n    {{ range .Params.variants }}\n        &lt;li&gt;{{ .sku }}, {{ (index $.Site.Data.inventory.gb.stock .sku).allocation  }}&lt;\/li&gt;\n    {{ end }}\n&lt;\/ul&gt;\nCela 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.\nNous 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 :\nBuilt site for language fr:\n0 draft content\n0 future content\n0 expired content\n1000 regular pages created\n8 other pages created\n0 non-page files copied\n50 paginator pages created\n0 tags created\n0 categories created\ntotal in 826 ms\n…mais ça reste acceptable, nous sommes toujours sous la seconde pour un site comportant 1000 pages produit.\nExemple d’architecture\nIl 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 :\n\n\n\n\n\n\nERP : 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.\nCMS : Drupal ou si possible une solution headless comme Contentful ou Directus. 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.\nHugo : éventuellement combiné à Gulp, Grunt ou équivalent pour la gestion des assets.\nPlate-forme Ecommerce : 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.\n\nDéploiement\nPour 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.\n\n\n\n\n\nLes 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.\nPour les besoins de ma preuve de concept, j'ai testé si Netlify 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.\nAfin 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.\nBuilt site for language fr:\n0 draft content\n0 future content\n0 expired content\n1000 regular pages created\n8 other pages created\n0 non-page files copied\n50 paginator pages created\n0 tags created\n0 categories created\ntotal in 834 ms\n9:10:18 PM: Build complete: exit code: 0\n9:10:19 PM: Cleaning up docker container\n9:10:19 PM: Starting to deploy site from 'public'\n9:10:19 PM: Deploying to CDN\n9:10:22 PM: Uploading 5 files\n9:10:22 PM: Uploading file product\/cadogan-tobacco-calf-16\/index.html\n9:10:22 PM: Uploading file product\/cadogan-tobacco-calf-340\/index.html\n9:10:22 PM: Uploading file product\/cadogan-tobacco-calf-1\/index.html\n9:10:22 PM: Uploading file product\/cadogan-tobacco-calf-267\/index.html\n9:10:22 PM: Uploading file product\/cadogan-tobacco-calf-102\/index.html\n9:10:23 PM: Starting post processing\n9:10:28 PM: Finished uploading cache in 473.751738ms\n9:10:28 PM: Post processing done\n9:10:28 PM: Site is live\n9:10:28 PM: Finished processing build request in 20.097144616s\nQuestions en suspend\nNous approchons du but, cependant il reste encore des questions auxquelles nous n'avons pas encore totalement répondu, par exemple :\nWorkflow\nDes 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.\nMises à jour de l’inventaire en temps réel\nLes 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.\nAperçu et préproduction\nCela pourrait s'avérer problématique si des développeurs, des contributeurs et des processus automatisés mettent tous le code à jour.\nPour aller plus loin\nVoilà 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.\nEn plus des aspects liés à la sécurité, à la performance et à la montée en charge déjà cités, j'aimerais aussi ajouter :\n\nGestion de contenu : 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.\nFlexibilité : les modèles de données dans Hugo sont très flexibles et le langage de gabarits de pages 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.\nSimplicité : 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.\n\nJe 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.",
      "content_html": "<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>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<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>\n<h2 id=\"avantages\">Avantages</h2>\n<p>Les avantages des sites statiques sont clairement documentés, et s'il fallait résumer :</p>\n<ul>\n<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>\n<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>\n<li><strong>Sécurité</strong> : Tout est dans le code source des pages HTML.</li>\n</ul>\n<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>\n<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>\n<h2 id=\"prise-de-conscience\">Prise de conscience</h2>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"les-fonctionnalites-dynamiques\">Les fonctionnalités dynamiques</h2>\n<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>\n<h2 id=\"le-panier-d-achat\">Le panier d’achat</h2>\n<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>\n<h2 id=\"en-resume\">En résumé</h2>\n<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>\n<p>Pour y parvenir, notre architecture extensible ecommerce statique doit répondre à plusieurs critères :</p>\n<ul>\n<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>\n<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>\n</ul>\n<h2 id=\"hugo\">Hugo</h2>\n<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>\n<p>Afficher les informations d’un produit se fait très simplement :</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p</span>&gt;</span>\n   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>\n   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>\n   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>\n   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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">p</span>&gt;</span></code></pre>\n<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>\n<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>) }}\n{{ partial $f1path}}\n{{ $f2path := (<span class=\"hljs-built_in\">print</span> <span class=\"hljs-string\">\"shoefeatures/\"</span> $.Params.feature2 <span class=\"hljs-string\">\".html\"</span>) }}\n{{ partial $f2path}}\n{{ $f3path := (<span class=\"hljs-built_in\">print</span> <span class=\"hljs-string\">\"shoefeatures/\"</span> $.Params.feature3 <span class=\"hljs-string\">\".html\"</span>) }}\n{{ partial $f3path}}\n{{ $f4path := (<span class=\"hljs-built_in\">print</span> <span class=\"hljs-string\">\"shoefeatures/\"</span> $.Params.feature4 <span class=\"hljs-string\">\".html\"</span>) }}\n{{ partial $f4path}}</code></pre>\n<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>\n<h2 id=\"performance\">Performance</h2>\n<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>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">Built site <span class=\"hljs-keyword\">for</span> language fr:\n0 draft content\n0 future content\n0 expired content\n1000 regular pages created\n8 other pages created\n0 non-page files copied\n50 paginator pages created\n0 tags created\n0 categories created\ntotal <span class=\"hljs-keyword\">in</span> 724 ms</code></pre>\n<p>Je trouve cela très impressionnant. Il y a tout de même quelques points à prendre en compte :</p>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<h2 id=\"les-donnees\">Les données</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<pre><code class=\"language-json hljs json\"><span class=\"hljs-string\">\"variants\"</span>: [\n      {\n        <span class=\"hljs-attr\">\"title\"</span>: <span class=\"hljs-string\">\"6\"</span>,\n        <span class=\"hljs-attr\">\"sku\"</span>: <span class=\"hljs-string\">\"9000-6\"</span>\n      },\n      {\n        <span class=\"hljs-attr\">\"title\"</span>: <span class=\"hljs-string\">\"7\"</span>,\n        <span class=\"hljs-attr\">\"sku\"</span>: <span class=\"hljs-string\">\"9000-7\"</span>\n      },\n      {\n        <span class=\"hljs-attr\">\"title\"</span>: <span class=\"hljs-string\">\"8\"</span>,\n        <span class=\"hljs-attr\">\"sku\"</span>: <span class=\"hljs-string\">\"9000-8\"</span>\n      },\n      {\n        <span class=\"hljs-attr\">\"title\"</span>: <span class=\"hljs-string\">\"9\"</span>,\n        <span class=\"hljs-attr\">\"sku\"</span>: <span class=\"hljs-string\">\"9000-9\"</span>\n      }\n    ]</code></pre>\n<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>\n<pre><code class=\"language-json hljs json\">{\n <span class=\"hljs-attr\">\"stock\"</span>: {\n    <span class=\"hljs-attr\">\"9000-1\"</span>: {\n     <span class=\"hljs-attr\">\"allocation\"</span>: <span class=\"hljs-number\">3</span>\n    },\n    <span class=\"hljs-attr\">\"9000-2\"</span>: {\n     <span class=\"hljs-attr\">\"allocation\"</span>: <span class=\"hljs-number\">9</span>\n    },\n    <span class=\"hljs-attr\">\"9000-3\"</span>: {\n     <span class=\"hljs-attr\">\"allocation\"</span>: <span class=\"hljs-number\">3</span>\n    },\n    <span class=\"hljs-attr\">\"9000-4\"</span>: {\n     <span class=\"hljs-attr\">\"allocation\"</span>: <span class=\"hljs-number\">1</span>\n    },\n    …\n  }\n}</code></pre>\n<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>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">&lt;ul&gt;\n    {{ <span class=\"hljs-keyword\">range</span> .Params.variants }}\n        &lt;li&gt;{{ .sku }}, {{ (index $.Site.Data.inventory.gb.stock .sku).allocation  }}&lt;/li&gt;\n    {{ end }}\n&lt;/ul&gt;</code></pre>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">Built site <span class=\"hljs-keyword\">for</span> language fr:\n0 draft content\n0 future content\n0 expired content\n1000 regular pages created\n8 other pages created\n0 non-page files copied\n50 paginator pages created\n0 tags created\n0 categories created\ntotal <span class=\"hljs-keyword\">in</span> 826 ms</code></pre>\n<p>…mais ça reste acceptable, nous sommes toujours sous la seconde pour un site comportant 1000 pages produit.</p>\n<h2 id=\"exemple-d-architecture\">Exemple d’architecture</h2>\n<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>\n<picture>\n<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\">\n<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\">\n<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;\">\n</picture>\n<ul>\n<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>\n<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>\n<li><strong>Hugo</strong> : éventuellement combiné à Gulp, Grunt ou équivalent pour la gestion des assets.</li>\n<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>\n</ul>\n<h2 id=\"deploiement\">Déploiement</h2>\n<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>\n<picture>\n<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\">\n<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\">\n<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;\">\n</picture>\n<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>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">Built site <span class=\"hljs-keyword\">for</span> language fr:\n0 draft content\n0 future content\n0 expired content\n1000 regular pages created\n8 other pages created\n0 non-page files copied\n50 paginator pages created\n0 tags created\n0 categories created\ntotal <span class=\"hljs-keyword\">in</span> 834 ms\n9:10:18 PM: Build complete: <span class=\"hljs-built_in\">exit</span> code: 0\n9:10:19 PM: Cleaning up docker container\n9:10:19 PM: Starting to deploy site from <span class=\"hljs-string\">'public'</span>\n9:10:19 PM: Deploying to CDN\n9:10:22 PM: Uploading 5 files\n9:10:22 PM: Uploading file product/cadogan-tobacco-calf-16/index.html\n9:10:22 PM: Uploading file product/cadogan-tobacco-calf-340/index.html\n9:10:22 PM: Uploading file product/cadogan-tobacco-calf-1/index.html\n9:10:22 PM: Uploading file product/cadogan-tobacco-calf-267/index.html\n9:10:22 PM: Uploading file product/cadogan-tobacco-calf-102/index.html\n9:10:23 PM: Starting post processing\n9:10:28 PM: Finished uploading cache <span class=\"hljs-keyword\">in</span> 473.751738ms\n9:10:28 PM: Post processing <span class=\"hljs-keyword\">done</span>\n9:10:28 PM: Site is live\n9:10:28 PM: Finished processing build request <span class=\"hljs-keyword\">in</span> 20.097144616s</code></pre>\n<h2 id=\"questions-en-suspend\">Questions en suspend</h2>\n<p>Nous approchons du but, cependant il reste encore des questions auxquelles nous n'avons pas encore totalement répondu, par exemple :</p>\n<h3 id=\"workflow\">Workflow</h3>\n<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>\n<h3 id=\"mises-a-jour-de-l-inventaire-en-temps-reel\">Mises à jour de l’inventaire en temps réel</h3>\n<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>\n<h3 id=\"apercu-et-preproduction\">Aperçu et préproduction</h3>\n<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>\n<h2 id=\"pour-aller-plus-loin\">Pour aller plus loin</h2>\n<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>\n<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>\n<ul>\n<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>\n<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>\n<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>\n</ul>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/12/05/gatsby-contentful-netlify-algolia/",
      "url": "https://jamstatic.fr/2017/12/05/gatsby-contentful-netlify-algolia/",
      "title": "Gatsby + Contentful + Netlify et Algolia",
      "summary": "Retour d’expérience sur la mise en place d’un site statique à l’aide de Contentful, Gatsby, Algolia et Netlify pour une qualité et un coût défiant toute concurrence.\n",
      "date_published": "2017-12-05T00:00:00+00:00","content_text": "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 Josh Weaver sur la migration d’un site de documentation vient s'ajouter à la longue liste des heureux convertis à la Jamstack.\nGatsby connaît un vif succès et une adoption croissante depuis peu et ce pour de bonnes raisons. C’est un outil suffisamment flexible pour s'adapter à pas mal de situations.\nSi 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.\nL'exemple que je vais couvrir ici est le site de documentation pour le principal logiciel édité par notre entreprise. Le site regroupe beaucoup de contenus avec des centaines d’articles.\nLa liste de prérequis pour ce site est la suivante :\n\nVitesse — autant pour le développement que pour la performance du site\nFacilité d’utilisation — autant pour le développeur que pour le contributeur\nRecherche — c'est un site de documentation après tout\nHébergement pas cher — maximisation de la valeur (qui ne la recherche pas ?)\nDéploiement en continu automatisé\n\nVoici donc un retour d’expérience global sur l’utilisation de Gatsby avec Contentful, Netlify et Algolia, les problèmes qu'ils aident à résoudre — sans mettre le nez dans le code.\nJe 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 les avantages offerts par les sites statiques et la Jamstack. Si ce n'est pas le cas, allez faire un tour sur jamstack.org pour comprendre en détail pourquoi le développement web, à défaut d’une meilleure formule, \"revient aux sources\".\nJe 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.\nMais si les sites statiques possèdent de nombreux avantages, ils ne sont pas dépourvus de nouveaux défis à relever.\nBien 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 plugins de sources de données.\nAvant 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 nombreux tweets qui clament la même chose, je pense que c'est un sentiment partagé.\nOK, revenons aux problèmes posés par le statique.\nNotre 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.\nIl existe un plugin Gatsby-Source-WordPress 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.\nContentful 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 :\n\nUne interface utilisateur attractive et intuitive,\nUn modèle de contenu simple,\nUne formule gratuite.\n\nL'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 de nouvelles fonctionnalités qui permettent de rechercher et de filtrer les articles encore plus facilement.\nContentful propose également une formule gratuite généreuse 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.\nMaintenant que je vous ai fait ce petit topo, comment Contentful vient s'interfacer avec un site sous Gastsby ?\nNotre 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.\n\n\n\n\n\n\nNavigation documentation.\n\nLa 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 seul 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.\nAprès avoir installé le plugin gatsby-source-contentful avec NPM et avoir ajouté vos paramètres de connexion à l’API de Contentful au fichier gatsby-config, on va pouvoir enfin s'amuser.\nDès que vous lancez la commande develop ou build 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.\nJ'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.\nÀ 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.\nLes 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.\nUn 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 lunr.js) qui vont prendre une requête et parcourir un index pré-construit au format JSON de votre contenu.\nJ'aurais pu créer cet index en allant taper dans la méthode onPostBuild 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.\nJ'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.\nLa 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.\nLe plus difficile était de savoir comment découper le contenu des articles en petits morceaux pour respecter les prérequis de l’indexation. La documentation d’Algolia 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.\nJ'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 onPostBuild 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 h), 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.\nC'était pas super propre, mais ça marchait.\nJ'ai fini par coupler la méthode d’indexation avec React InstantSearch. 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.\nSympa.\nToutefois 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é.\nL'ironie a voulu que je découvre DocSearch 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é.\n\n\n\n\n\n\nAlgolia DocSearch FTW.\n\nEt ç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 code source 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.\nUn 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.\nMais 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.\nSi 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 commit 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.\nEst-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 ?\nHeureusement je connaissais déjà les réponses à ces questions, car j'avais déjà découvert Netlify à l’occasion de projets précédents.\nBrancher 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 !\nNetlify a récemment revu ses tarifs 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 :\n\nFormule gratuite dans le cadre de projets personnels ou commerciaux (c'est vraiment une super offre gratuite),\nactivation du HTTPS en un clic grâce à Let's Encrypt,\nréseau de CDN ultra-rapide,\nsupport des noms de domaines personnalisés,\ndéploiements automatiques\nun moteur de génération intégré super cool,\net bien plus…\nEt si je vous dis que tout ça est GRATUIT ?\n\nVoyons maintenant son utilisation avec Gatsby.\nAprè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 gatsby build\", ensuite il va respecter ce qui est défini dans le fichier package.json (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.\nEt 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.\nNetlify c'est le robot magique qui résout votre problème de déploiement et d’hébergement.\nAssocié à 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.\n\n\n\n\n\n\nIndicateurs de performance de la page.\n\nPour 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.\nContentful propose une fonctionnalité de webhook 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.\n\n\n\n\n\n\nDéclenchement de la génération par webhook.\n\nC’est l’association rêvée au paradis de la Jamstack.\nLa 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 lazy loading, 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 ?\".\nMaintenant, j'espère que nos clients partageront ce sentiment avec notre site.",
      "content_html": "<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>\n<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>\n<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>\n<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>\n<p>La liste de prérequis pour ce site est la suivante :</p>\n<ul>\n<li>Vitesse — autant pour le développement que pour la performance du site</li>\n<li>Facilité d’utilisation — autant pour le développeur que pour le contributeur</li>\n<li>Recherche — c'est un site de documentation après tout</li>\n<li>Hébergement pas cher — maximisation de la valeur (qui ne la recherche pas ?)</li>\n<li>Déploiement en continu automatisé</li>\n</ul>\n<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>\n<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>\n<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>\n<p>Mais si les sites statiques possèdent de nombreux avantages, ils ne sont pas dépourvus de nouveaux défis à relever.</p>\n<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>\n<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>\n<p>OK, revenons aux problèmes posés par le statique.</p>\n<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>\n<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>\n<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>\n<ul>\n<li>Une interface utilisateur attractive et intuitive,</li>\n<li>Un modèle de contenu simple,</li>\n<li><a href=\"https://www.contentful.com/pricing/\" target=\"_blank\" rel=\"noopener noreferrer\">Une formule gratuite</a>.</li>\n</ul>\n<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>\n<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>\n<p>Maintenant que je vous ai fait ce petit topo, comment Contentful vient s'interfacer avec un site sous Gastsby ?</p>\n<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>\n<figure>\n<picture title=\"Navigation documentation.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Navigation documentation.</figcaption>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<p>C'était pas super propre, mais ça marchait.</p>\n<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>\n<p>Sympa.</p>\n<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>\n<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>\n<figure>\n<picture title=\"Algolia DocSearch FTW.\">\n<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\">\n<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\">\n<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,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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/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\">\n</picture>\n<figcaption>Algolia DocSearch FTW.</figcaption>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<ul>\n<li>Formule gratuite dans le cadre de projets personnels ou commerciaux (c'est vraiment une super offre gratuite),</li>\n<li>activation du HTTPS en un clic grâce à Let's Encrypt,</li>\n<li>réseau de CDN ultra-rapide,</li>\n<li>support des noms de domaines personnalisés,</li>\n<li>déploiements automatiques</li>\n<li>un moteur de génération intégré super cool,</li>\n<li><a href=\"https://www.netlify.com/features/\" target=\"_blank\" rel=\"noopener noreferrer\">et bien plus…</a></li>\n<li>Et si je vous dis que tout ça est GRATUIT ?</li>\n</ul>\n<p>Voyons maintenant son utilisation avec Gatsby.</p>\n<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>\n<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>\n<p>Netlify c'est le robot magique qui résout votre problème de déploiement et d’hébergement.</p>\n<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>\n<figure>\n<picture title=\"Indicateurs de performance de la page.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Indicateurs de performance de la page.</figcaption>\n</figure>\n<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>\n<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>\n<figure>\n<picture title=\"Déclenchement de la génération par webhook.\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Déclenchement de la génération par webhook.</figcaption>\n</figure>\n<p>C’est l’association rêvée au paradis de la Jamstack.</p>\n<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>\n<p>Maintenant, j'espère que nos clients partageront ce sentiment avec notre site.</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/12/02/entretien-michael-rose/",
      "url": "https://jamstatic.fr/2017/12/02/entretien-michael-rose/",
      "title": "Entretien avec Michael Rose, designer et développeur front-end",
      "summary": "Michael Rose, webdesigner très actif dans la communauté Jekyll, partage son retour d’expérience sur les générateurs de site statique.",
      "date_published": "2017-12-02T00:00:00+00:00","content_text": "Michael Rose est un des webdesigner les plus actifs dans la communauté Jekyll, il est l’auteur de thèmes populaires comme Minimal Mistakes, Basically Basic ou Hpster. 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.\n\n\n\n\n\n\nMichael Rose\n\nBonjour Michael, comment te présenter ?\nLa 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.\nQuand 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.\nComment en es-tu venu à adopter Jekyll comme outil de publication ?\nLe fait que Jekyll utilise Liquid 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\".\nJe 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.\nBien 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é.\nSur quels types de sites travailles-tu ?\nDans 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.\nEn dehors du boulot, je m'occupe de quelques blogs WordPress avec beaucoup de contenus, mon site personnel est sous Jekyll, et je maintiens plusieurs thèmes populaires pour Jekyll.\nQu'est-ce qui te plait dans le fait de travailler avec des générateurs de site statique ?\nIl y a trois chose que j'aime bien quand je travaille avec des GSS :\n\nLeur simplicité. 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.\nLeur interopérabilité. La plupart des GSS utilisent des fichiers Markdown pour stocker les contenus, ce qui rend les migrations vers d’autres générateurs beaucoup plus simples.\nLeur performance. 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.\n\nQuelle est ta fonctionnalité préférée dans Jekyll ?\nL'utilisation de Liquid. J'ai essayé GSS et Jekyll est de loin le plus facile à apprendre et avec lequel on peut vite construire des trucs.\nQuelle est la fonctionnalité qui te manque le plus ?\nComme 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.\nLe 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.\nMis à part les différences de langages dans lequel ils sont développés, l’expérience est la même entre les différents GSS. 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 Forestry.io, Cloudcannon, et même le plugin jekyll-admin ce vide a été en partie rempli.\nJ'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 ;-)\nJ'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).\nJ'ai pas mal étudié Gatsby 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 srcset grâce aux plugins gatsby-remark-images et gatsby-images et ce de manière assez rapide à l’aide de sharp.\nJe n'ai pas testé la version 3 du plugin jekyll-assets 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.\nPourquoi y a-t-il aussi peu de thèmes de qualité pour Jekyll selon toi?\nJusqu'à 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 forker 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 gratuit avec lequel ils pourront publier des articles.\nMaintenant que GitHub Pages supporte les thèmes distants 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.\nL'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 développement de thème. 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 _layouts, les variables de configuration à ajouter au fichier _config.yml et un support natif de i18n dans Jekyll, aiderait pas mal je pense.\nSur mes derniers thèmes, j'ai essayé de me baser sur ce qu'à fait Minima, le thème par défaut de Jekyll. 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.\nIl 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.\nPourquoi n'y a-t-il pas plus de designers Web qui travaillent avec des générateurs de site statique ?\nJe 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 ;)\nIl est possible que beaucoup pensent que c'est juste pour faire de petits sites perso. Avec tous les services qui arrivent comme Netlify et les lancements de site visibles comme Smashing Magazine avec Hugo, 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.\nTon thème hpster a été porté pour Hugo, est-ce que tu as testé ?\nOui 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.\nLa seule chose qui me retienne véritablement de creuser un peu plus dans Hugo est son langage pour les gabarits de page. Ç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.",
      "content_html": "<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>\n<figure>\n<picture title=\"Michael Rose\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Michael Rose</figcaption>\n</figure>\n<h2 id=\"bonjour-michael-comment-te-presenter\">Bonjour Michael, comment te présenter ?</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"sur-quels-types-de-sites-travailles-tu\">Sur quels types de sites travailles-tu ?</h2>\n<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>\n<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>\n<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>\n<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>\n<ol>\n<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>\n<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>\n<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>\n</ol>\n<h2 id=\"quelle-est-ta-fonctionnalite-preferee-dans-jekyll\">Quelle est ta fonctionnalité préférée dans Jekyll ?</h2>\n<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>\n<h2 id=\"quelle-est-la-fonctionnalite-qui-te-manque-le-plus\">Quelle est la fonctionnalité qui te manque le plus ?</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/11/27/entretien-bud-parr/",
      "url": "https://jamstatic.fr/2017/11/27/entretien-bud-parr/",
      "title": "Entretien avec Bud Parr de The New Dynamic",
      "summary": "Bud Parr, créateur et gérant de l’agence The New Dynamic, partage son expérience et sa vision sur l’écosystème actuel de la Jamstack.",
      "date_published": "2017-11-27T00:00:00+00:00","content_text": "Bud Parr est impliqué dans la communauté qui gravite autour des générateurs de site statique depuis plusieurs années. Il possède sa propre agence et agrège sa curation autour de l’écosystème de la Jamstack sur thenewdynamic.org. Bud organise également des meetups à New-York et il est également à l’origine du récent redesign du site d’Hugo. Il a gentiment accepté de répondre à nos questions.\n\n\n\n\n\n\nBud Parr\n\nBonjour 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 ?\nPour ê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 éditeurs 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 CMS headless 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.\nNous avons aussi assisté à l’émergence du Serverless d’Amazon ou des \"Cloud functions\" de Google 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.\nEn 2015 Tom Preston-Werner disait lors de la JekyllConf que 80% du Web pourrait être statique, est-ce le cas aujourd’hui ?\nPeter Levine, un des associés d’Andreessen Horowitz, fait également ce constat lorsqu'il écrit :\n&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.\nPar Netlify, il sous-entend déployé en statique ou basé sur la Jamstack. Il me semble que nous sommes encore loin du compte.\nDivisons 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.\nLes administrations publiques aux États-Unis (avec 18F), au Royaume-Uni (avec Alphagov) ou en France (avec Etalab) semblent privilégier ce type de workflow. Pourquoi à ton avis est-ce si intéressant pour les administrations publiques et les organismes ouverts ?\nCe 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.\nNous 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 ?\nL'é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 une galerie sur The New Dyanmic 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 bloguer comme un hacker, on peut utiliser ces outils pour maintenir de la documentation, présenter des services et des produits et faire des choses plus sophistiquées. Smashing Magazine 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.\nMais alors pourquoi les agences n'adoptent-elles pas ce processus de travail ?\nC’est difficile à dire pour moi, vu que j'ai choisi de travailler uniquement 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.\nSi 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 ?\nTout à 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 : Forestry.io, Siteleaf, Cloudcannon et Netlify CMS par exemple. Jusqu'à encore récemment il n'y avait que quelques acteurs majeurs du côté des APIs de contenu (comme Contentful ou Prismic), maintenant il existe des dizaines de CMS headless 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 !\nQuel est ton workflow actuel ? Qu'est-ce qui te plaît tant dedans ?\nEn ce moment j'utilise des services et des outils qui me permettent de répondre à des projets variés : Hugo pour la génération de site, la bibliothèque CSS Tachyons pour construire des interfaces, Webpack pour la gestion des assets, Netlify pour le déploiemet et l’hébergement, Forestry pour que mes clients puissent rédiger leurs contenus.\nJe 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.\nCela 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.\nPar exemple, pour le projet www.retroreport.org que j'ai réalisé avec Hugo, je me suis reposé sur les formats d’export personnalisés d’Hugo 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.\n\n\n\n\n\n\nretroreport.org\n\nComment vends-tu cette manière de travailler à tes clients ?\nJe 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.\nQue souhaites-tu pour cette stack Web moderne à court terme ?\nJe 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 Single Page Applications. 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.",
      "content_html": "<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>\n<figure>\n<picture title=\"Bud Parr\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Bud Parr</figcaption>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<p>Peter Levine, un des associés d’Andreessen Horowitz, fait également ce constat lorsqu'il écrit :\n<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>\nPar 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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"retroreport.org\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption><a href=\"https://www.retroreport.org\" target=\"_blank\" rel=\"noopener noreferrer\">retroreport.org</a></figcaption>\n</figure>\n<h2 id=\"comment-vends-tu-cette-maniere-de-travailler-a-tes-clients\">Comment vends-tu cette manière de travailler à tes clients ?</h2>\n<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>\n<h2 id=\"que-souhaites-tu-pour-cette-stack-web-moderne-a-court-terme\">Que souhaites-tu pour cette stack Web moderne à court terme ?</h2>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/10/03/creer-un-theme-jekyll/",
      "url": "https://jamstatic.fr/2017/10/03/creer-un-theme-jekyll/",
      "title": "Créer votre premier thème pour Jekyll",
      "summary": "Un guide complet pour installer et créer un thème pour Jekyll par David Darnes.",
      "date_published": "2017-10-03T12:45:36+00:00",
      "date_modified": "2018-01-17T09:10:00+00:00","content_text": "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 Liquid 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, David Darnes, développeur du thème Alembic, explique comment utiliser une 💎 gem de thème pour Jekyll, puis comment développer la vôtre.\n\n\n\n\n\nTout 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.\nLes 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 gems de thèmes, les thèmes s’installent maintenant comme les plugins à l’aide de bundler.\nComment fonctionnent les thèmes ?\nLes thèmes pour Jekyll permettent de packager tous les modèles et les fichiers relatifs à la présentation dans une gem Ruby, 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.\nExemple de structure de site sous Jekyll utilisant une gem de thème :\nsource\/\n├── _posts\/\n│   └── mon-super-article-14-09-2017.md\n├── index.html\n├── Gemfile\n├── _config.yml\n├── 404.md\n└── a-propos.md&lt;\/code&gt;&lt;\/pre&gt;\nIl est également possible de prendre le pas sur les fichiers de thèmes, un peu à la manière des thèmes enfants dans WordPress. 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.\nComment utiliser un thème ?\nInstaller un thème est vraiment très simple, mais si vous découvrez Jekyll, vous hésitez peut-être encore à les tester.\nD’abord, il faut ajouter le thème que vous voulez utiliser à la liste des gems que vous utilisez pour votre site :\n# La gem de base pour Jekyll\ngem \"jekyll\" \"~&gt; 3\"\n\n# La gem du thème que vous souhaitez utiliser\ngem \"alembic-jekyll-theme\", \"~&gt; 2.2\"\n\n# Les plugins que vous utilisez\ngroup :jekyll_plugins do\n  gem \"jekyll-sitemap\"\n  gem \"jekyll-paginate\"\n  gem \"jekyll-seo-tag\"\nend\nLe code ci-dessous est un exemple de fichier Gemfile. Ce fichier Gemfile sert à gérer les gems de votre projet avec l’aide de Bundler. Ici j’utilise le thème alembic-jekyll-theme, ainsi que d’autres plugins pour Jekyll.\nEnsuite, il faut déclarer l’utilisation du thème dans votre fichier de configuration _config.yml:\ntheme: alembic-jekyll-thème\nUne fois que vous avez modifié ces deux fichiers, il va falloir utiliser Bundler pour installer notre nouveau thème et pouvoir générer et prévisualiser notre site. Dans votre terminal tapez la commande suivante :\nbundle\nCela va installer les gems manquantes, puis tapez…\nbundle exec jekyll build\n…pour générer le site avec Jekyll.\nPour des exemples plus avancés consultez la documentation de Siteleaf sur la gestion des thèmes Jekyll.\nQuels thèmes puis-je utiliser ?\n\n\n\n\n\nDe 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 'jekyll-theme' sur rubygems.org.\nPour ma part j’en ai développé deux :\n\nAlembic - un thème prêt à l’emploi, qui peut aussi servir de point de départ pour votre projet,\nGarth - un thème de blog très simple.\n\nCes deux thèmes sont compatibles avec Siteleaf, vous pouvez donc configurer un nouveau site sur Siteleaf sans problème. Je vous recommande aussi Minimal Mistakes, 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.\nPour ceux d’entre vous qui utilisent GitHub Pages pour héberger leur site Jekyll, seuls quelques thèmes sont autorisés par défaut.\nC’est en partie la raison pour laquelle, selon moi, les thèmes n’ont peut-être pas encore l’ampleur qu’ils pourraient avoir.\nBeaucoup 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 Siteleaf Team+ plan qui vous permet d’utiliser n’importe quel thème Jekyll et n’importe quel plugin.[^custom-plugins]\nTrucs et astuces pour créer un super thème\nSi vous avez envie de développer votre propre thème, permettez-moi de partager avec vous ce que mon expérience m’a enseigné.\n\n\n\n\n\n\nchecklist d’un thème jekyll.\n\nVoici 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) :\n\nTestez votre thème : 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.\nFournissez une bonne documentation : 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 documentation de l’utilisation d’Alembic à jour.\nÉvitez les choses trop complexes : 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.\nDéfinissez un usage : Concevoir un thème susceptible de plaire au plus grand monde et à 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.\nConcevez avec l’extensibilité en tête : 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 des conventions, et utilisez des noms explicites pour vos _includes (par exemple icon.html si c’est pour insérer une icône).\n\nMaintenant 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.\nBien configurer son environnement\nAvant 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.\nJekyll 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 :\ngem install bundler jekyll\nLa documentation officielle propose une méthode pour installer Jekyll sur une machine Windows.\nSi vous préférez utiliser la gem de GitHub en vue d’utiliser GitHub Pages, vous serez limité aux gems supportées par cette plate-forme.\nVous aurez dans tous les cas besoin de Bundler, pour la gestion des gems utilisées par votre thème.\nEnfin, si vous souhaitez proposer votre thème sous forme de gem au public, vous aurez besoin d’un compte sur RubyGems.org.\nC’est parti\nNous allons commencer par créer une base pour notre thème à l’aide de la commande new-theme de Jekyll :\njekyll new-theme mon-theme\nCette 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é mon-theme.\nNous 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 .gemspec qui porte le nom de votre thème. Les deux champs à renseigner sont :\nspec.summary       = \"Une brève description de mon thème\"\nspec.homepage      = \"http:\/\/url-de-mon-theme.com\"\nUne fois que c’est fait et que vous avez sauvegardé vos changements, nous pouvons installer les gems dont dépend notre thème.\nVous remarquerez que plus bas dans le fichier .gemspec, il y a des lignes qui commencent par spec.add_runtime_dependency et spec.add_development_dependency. C’est ici que nous allons pouvoir spécifier les gems dont notre thème aura besoin pour fonctionner : runtime quand le thème est utilisé et comme son nom l’indique development pour le développement du thème à proprement parlé. L’installation des dites gems se fait ensuite via la commande :\nbundle\nPour prévisualiser votre thème et vous assurer qu’il fonctionne bien, vous devez avoir un fichier index.html à la racine de votre répertoire avec quelque chose comme :\n---\ntitle: Accueil\nlayout: home\n---\n\n# Du Markdown en plus (ou pas)\nCe 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 :\nbundle exec jekyll serve\nSi vous utilisez Jekyll v3.7.0, vous pouvez passer l’option --livereload en paramètre pour que votre navigateur rafraîchisse automatiquement la page après modifications des fichiers.\nLa sortie sur la console devrait ressembler à ça :\n$ bundle exec jekyll serve --livereload\nConfiguration file: none\n            Source: \/Users\/frank\/code\/jekyll\/themes\/mon-super-theme\n       Destination: \/Users\/frank\/code\/jekyll\/themes\/\/mon-super-theme\/_site\n Incremental build: disabled. Enable with --incremental\n      Generating…\n                    done in 0.095 seconds.\n Auto-regeneration: enabled for '\/Users\/frank\/code\/jekyll\/themes\/mon-super-theme'\nLiveReload address: http:\/\/127.0.0.1:35729\n    Server address: http:\/\/127.0.0.1:4000\n  Server running… press ctrl-c to stop.\n        LiveReload: Browser connected\nPour ceux qui ne sont pas encore très familiers avec l’écosystème Ruby, préfixer la commande par bundle exec permet de nous assurer que nous utilisons bien les gems définies dans le fichier Gemfile du dossier courant. Ici comme nous travaillons sur une gem, il pointe vers le fichier .gemspec. Ainsi nous sommes dans la même configuration que les futurs utilisateurs de notre thème.\nLa structure de fichiers\nPour le moment nous avons donc la structure suivante :\n├── _includes\n├── _layouts\n│   ├── default.html\n│   ├── page.html\n│   ├── post.html\n├── _sass\n│   ├── LICENSE.txt\n│   ├── README.md\n│   ├── assets\n│   ├── index.html\n│   └── mon-super-theme.gemspec\n├── assets\n├── index.html\n├── mon-super-theme.gemspec\n├── Gemfile\n├── Gemfile.lock\n├── LICENSE.txt\n├── README.md\nVoyons à quoi servent les différents dossiers et fichiers présents :\n\n_includes : vide pour le moment, il sert à stocker les fichiers de gabarits partiels,\n_layouts : contient pour le moment trois exemples de gabarits : default.html, post.html and page.html,\n_sass : vide pour le moment, destiné à stocker vos fichiers Sass,\nassets : é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 styles.scss qui génèrera un fichier styles.css auquel nous ferons référence dans notre modèle de page,\nle fichier Gemfile - qui indique à Bundler quelles gems sont nécessaires, et qui pointe vers le fichier .gemspec,\nle fichier mon-super-theme.gemspec 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 spec.files. Vous n’avez pas besoin d’éditer cette liste, qui respecte déjà la convention standard des thèmes Jekyll,\ndes fichiers LICENSE.txt et README.md 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 bien documenter votre thème.\n\nVoilà 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 .gitignore et .gemspec.\nDévelopper votre thème\nLa 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 .gemspec et rappelez-vous que par défaut GitHub pages n’autorise qu’une liste limitée de plugins. Sachez que le formule Team plan de Siteleaf vous permet de vous affranchir de cette limitation, même chose chez Netlify.\nAjouter des contenus d’exemple\n\n\n\n\n\nNous venons d’ajouter un fichier index.html 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 _posts 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.\nAu cœur de tout site Jekyll, on trouve le fichier de configuration principal (_config.yml). 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 exemple de fichier de configuration 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 à la documentation officielle.\nSoumettre sa gem de thème\nUne 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 .gem va empaqueter tous vos modèles de page, vos styles dans un seul fichier. Il faudra ensuite publier ce fichier sur le RubyGems.org.\nPour générer votre gem, il vous suffit d’utiliser cette commande :\ngem build mon-super-theme.gemspec\nUne fois que c’est fait, un nouveau fichier est présent à la racine de votre projet - du type mon-super-theme-0.1.0.gem. La nomenclature correspond au nom de votre gem et au numéro de version indiqués dans votre fichier .gemspec. Une fois la gem générée, il ne reste plus qu’à la pousser en ligne avec la commande :\ngem push mon-super-theme-0.1.0.gem\nLors 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.\n\n\n\n\n\n\nExemple de page Rubygems\n\nLes thèmes distants sur GitHub Pages\nRécemment GitHub Pages a ajouté le support des thèmes distants, tout dépôt de thème Jekyll public sur GitHub peut être utilisé comme un thème Jekyll.\nL’installation d’un thème distant demande l’utilisation du plugin jekyll-remote-theme, qui est donc autorisé sur GitHub Page. Pour l’installer il vous faut déclarer le plugin dans votre fichier _config.yml et utiliser une clé spécifique remote_theme 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 :\nplugins:\n  - jekyll-remote-theme\n\nremote_theme: daviddarnes\/alembic\nEn 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.\nLe 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 :\nremote_theme: daviddarnes\/alembic@2.3.1\nTests et mises à jour\nUne fois votre thème en ligne, assurez-vous une dernière fois qu’il fonctionne comme n’importe quel autre thème Jekyll. Notez les difficultés qu’un utilisateur pourrait rencontrer.\nSi vous devez publier des corrections ou des mises à jour, vous allez devoir incrémenter le numéro de version de façon appropriée dans votre fichier .gemspec, générer une nouvelle version de votre gem et la publier sur Rubygems.org.\nN’hésitez pas à m’envoyer un tweet si vous avez des questions. Si vous utilisez Siteleaf, vous pouvez venir discuter avec la communauté sur http:\/\/chat.siteleaf.com\/ pour poser vos questions et partager votre travail.",
      "content_html": "<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>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<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>\n<h2 id=\"comment-fonctionnent-les-themes\">Comment fonctionnent les thèmes ?</h2>\n<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>\n<p><strong>Exemple de structure de site sous Jekyll utilisant une gem de thème :</strong></p>\n<pre><code>source/\n├── _posts/\n│   └── mon-super-article-14-09-2017.md\n├── index.html\n├── Gemfile\n├── _config.yml\n├── 404.md\n└── a-propos.md&lt;/code&gt;&lt;/pre&gt;</code></pre>\n<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>\n<h2 id=\"comment-utiliser-un-theme\">Comment utiliser un thème ?</h2>\n<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>\n<p>D’abord, il faut ajouter le thème que vous voulez utiliser à la liste des gems que vous utilisez pour votre site :</p>\n<pre><code class=\"language-ruby hljs ruby\"><span class=\"hljs-comment\"># La gem de base pour Jekyll</span>\ngem <span class=\"hljs-string\">\"jekyll\"</span> <span class=\"hljs-string\">\"~&gt; 3\"</span>\n\n<span class=\"hljs-comment\"># La gem du thème que vous souhaitez utiliser</span>\ngem <span class=\"hljs-string\">\"alembic-jekyll-theme\"</span>, <span class=\"hljs-string\">\"~&gt; 2.2\"</span>\n\n<span class=\"hljs-comment\"># Les plugins que vous utilisez</span>\ngroup <span class=\"hljs-symbol\">:jekyll_plugins</span> <span class=\"hljs-keyword\">do</span>\n  gem <span class=\"hljs-string\">\"jekyll-sitemap\"</span>\n  gem <span class=\"hljs-string\">\"jekyll-paginate\"</span>\n  gem <span class=\"hljs-string\">\"jekyll-seo-tag\"</span>\n<span class=\"hljs-keyword\">end</span></code></pre>\n<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>\n<p>Ensuite, il faut déclarer l’utilisation du thème dans votre fichier de configuration <code>_config.yml</code>:</p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">theme:</span> <span class=\"hljs-string\">alembic-jekyll-thème</span></code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">bundle</code></pre>\n<p>Cela va installer les gems manquantes, puis tapez…</p>\n<pre><code class=\"language-sh hljs bash\">bundle <span class=\"hljs-built_in\">exec</span> jekyll build</code></pre>\n<p>…pour générer le site avec Jekyll.</p>\n<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>\n<h2 id=\"quels-themes-puis-je-utiliser\">Quels thèmes puis-je utiliser ?</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<p>Pour ma part j’en ai développé deux :</p>\n<ul>\n<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>\n<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>\n</ul>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"trucs-et-astuces-pour-creer-un-super-theme\">Trucs et astuces pour créer un super thème</h2>\n<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>\n<figure>\n<picture title=\"checklist d’un thème jekyll.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>checklist d’un thème jekyll.</figcaption>\n</figure>\n<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>\n<ol>\n<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>\n<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>\n<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>\n<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>\n<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>\n</ol>\n<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>\n<h2 id=\"bien-configurer-son-environnement\">Bien configurer son environnement</h2>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">gem install bundler jekyll</code></pre>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"c-est-parti\">C’est parti</h2>\n<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>\n<pre><code class=\"language-sh hljs bash\">jekyll new-theme mon-theme</code></pre>\n<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>\n<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>\n<pre><code class=\"language-ruby hljs ruby\">spec.summary       = <span class=\"hljs-string\">\"Une brève description de mon thème\"</span>\nspec.homepage      = <span class=\"hljs-string\">\"http://url-de-mon-theme.com\"</span></code></pre>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">bundle</code></pre>\n<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>\n<pre><code class=\"language-md hljs markdown\">---\ntitle: Accueil\n<span class=\"hljs-section\">layout: home\n---</span>\n\n<span class=\"hljs-section\"># Du Markdown en plus (ou pas)</span></code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">bundle <span class=\"hljs-built_in\">exec</span> jekyll serve</code></pre>\n<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>\n<p>La sortie sur la console devrait ressembler à ça :</p>\n<pre><code class=\"language-sh hljs bash\">$ bundle <span class=\"hljs-built_in\">exec</span> jekyll serve --livereload\nConfiguration file: none\n            Source: /Users/frank/code/jekyll/themes/mon-super-theme\n       Destination: /Users/frank/code/jekyll/themes//mon-super-theme/_site\n Incremental build: disabled. Enable with --incremental\n      Generating…\n                    <span class=\"hljs-keyword\">done</span> <span class=\"hljs-keyword\">in</span> 0.095 seconds.\n Auto-regeneration: enabled <span class=\"hljs-keyword\">for</span> <span class=\"hljs-string\">'/Users/frank/code/jekyll/themes/mon-super-theme'</span>\nLiveReload address: http://127.0.0.1:35729\n    Server address: http://127.0.0.1:4000\n  Server running… press ctrl-c to stop.\n        LiveReload: Browser connected</code></pre>\n<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>\n<h2 id=\"la-structure-de-fichiers\">La structure de fichiers</h2>\n<p>Pour le moment nous avons donc la structure suivante :</p>\n<pre><code class=\"language-sh hljs bash\">├── _includes\n├── _layouts\n│   ├── default.html\n│   ├── page.html\n│   ├── post.html\n├── _sass\n│   ├── LICENSE.txt\n│   ├── README.md\n│   ├── assets\n│   ├── index.html\n│   └── mon-super-theme.gemspec\n├── assets\n├── index.html\n├── mon-super-theme.gemspec\n├── Gemfile\n├── Gemfile.lock\n├── LICENSE.txt\n├── README.md</code></pre>\n<p>Voyons à quoi servent les différents dossiers et fichiers présents :</p>\n<ul>\n<li><code>_includes</code> : vide pour le moment, il sert à stocker les fichiers de gabarits partiels,</li>\n<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>\n<li><code>_sass</code> : vide pour le moment, destiné à stocker vos fichiers Sass,</li>\n<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>\n<li>le fichier <code>Gemfile</code> - qui indique à Bundler quelles gems sont nécessaires, et qui pointe vers le fichier <code>.gemspec</code>,</li>\n<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>\n<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>\n</ul>\n<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>\n<h2 id=\"developper-votre-theme\">Développer votre thème</h2>\n<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>\n<h2 id=\"ajouter-des-contenus-d-exemple\">Ajouter des contenus d’exemple</h2>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n<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>\n<h2 id=\"soumettre-sa-gem-de-theme\">Soumettre sa gem de thème</h2>\n<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>\n<p>Pour générer votre gem, il vous suffit d’utiliser cette commande :</p>\n<pre><code class=\"language-sh hljs bash\">gem build mon-super-theme.gemspec</code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">gem push mon-super-theme-0.1.0.gem</code></pre>\n<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>\n<figure>\n<picture title=\"Exemple de page Rubygems\">\n<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\">\n<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\">\n<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,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAM20lEQVR4nNVba5rkqBGMQFTP2GfzwXwbn2+nS6R/kC8eUtXMTu/a9EcjAUKQQWQmWVX8z7//JYcQRys42oFDKg5UHHig4oHCisIHSnmA5QHWD7B+AB/fwMc3oH6A9QEcFSwHUApIRkkCAPp/AUTAJkBr4HkC5xN8fgKfP4AffwB//NHLzx89P5/A+QTOE5ATaKLjIEp9h6f5HrQJxH26lJidlvN9LuX+nnpPbaFAIBA2NDacPHsullsvD8FZBBXlAIQACsADEM3QTM3FcumZpS+c4+JcICSE7KAgz7hP2vvbGGQfs+R8AKXpfAA0fVb0eZF4KVdB397nCXO6fyvJ/t5loBuHOnEWAGdfZ9F+RfOBvs4iqKi1AyIFaAcgD0AqgEfP1FweQKnA8YAcD/CokHKApUBK6SVL3JuAXRDSJyMNaKVPRPoOcuEfB1Ar0FoInejtzeaXGTLLNgPwiiWp/wSIzvbNUtZ6ZQmVHVSGdGDOvlY+gXL2fJwdEAoqHh/KEGPHI/IASO2gHB0UVM1H1awMYgGKgTExCNLfxTYKT1WZg2ACLaWPe55AOzuYktTVIugbQJb7qT8A6liytLxKG7bQ6hUUNhAN5KmgPBUUBYYNYEPlx3d9/dGZoUAQUZI1QDk2+VKdTQwBQGmdjWzAmdUVAwQHoob9cNZs2LEV8Gb3D82cer5gC99hR75P7ICgsYE4QZ4oPNGogHg+QTRUfv8HiALKAaKCoiAkMLwsXfgsnRF+f3RmUMFg6cLtO46hViGAHB2UIkBp4HkA5dmfLSVAr48ORlN2NAlALtOVPXnjegtKv7Hh3jfuuaapcW8ATgieaDhBfqLwicYniCcYgPxzBAQPFOxAUS+Kh3pTR3hVDoSWZF+IrsTWTVU1NPUkDTwacB5dj561l+0Zaqo1tTvGjitAmEjAAGXLkI3XtfPCpmrluIs7/mcQZkgUEJwATjScKFAw8AnyszMHMyA4QFE24KMDw4eCVFHU2yKLgpLLrqJYigJgwkmlLUWsNBe4AUfrgjcAcindRR5c3StAAN/17t1xbVuup/sRt6h34Q+PTsBI3IsDogYdJwqeEPnsoGhueAYg5ds/AhBUFAdDS1QUVJDah8aC4jqfgwEPV9fLaaGUtNubKAMSE/w6243s6q5YcBa6q0zvMAn+mhUDKP4It1thzxYEHCIKSGdIkScEP1DkE8QPEJ8o8gnBCUBQy7fvCZAHyIeC8k3ZUkEeKCgKSjfWVg4G2W1GUh/ZhpjKMsEuuQVA7nW9AGMBYbzvhc0NY+kFF2xWLyv1mRpdQdk0Uz0haGiAnACeHRB5gPIHCg4UqWioKNK9yMrHd5BUtVRVXX2g4JurrKLM6DAoEFgZgWHhGIFxQHTKftLW6wwMpuvdagfZXqmrDSB2nWzb0HetukhhS6ybLykBYoYdOCHyRJFPNDlQWkGRgoJeipwoaKh8PFTYR7CD3xIwVdsNDsYCHZQ8+wtAJC0224Jh9ydw5vadu/sWM1aARnVmxQTILRi+Op+nu766TnN9GwTFATkgUlCEKOp4UQhKQZEnmgMCAubeDq6uGXUFgwFKAJIWk1VBunSyLwxJlTuQhr4jGi6+pIZmm5HvOdcjAYVV/nfsuHS8U0MHRVBUZVGZ0ISgOjNFGiiCIoAIQWmoPI4ucFYAVYHpdqPblUOBSCprZomvaAVDxbFZyUYFYQfEnQg4boLFmVgBCUEnd3yYsu7vDSA5mjPPeu1uc26xoR2MEyw9WEp1XigCNRzqtqIoMAVkDywaEFYPHXgEZJrireLlRrZzxf39pbGd1dfEktGeZUZH3VaoXIFYNGeqy6cU6n9KA/X8RenypRxg6+Eq8nBbWi3EQdh5ooz37l0dyB7WOP1kuW+V713bK2CuRopN4SwAElty3Sj2tc3GG70mCxbeTWs28YQFUpq+qKj3yJ5bj0xQNHCKgh5czO4qjSFMhk8HIxNb0i5cpjTX76cfSTZ1r59a6rKX9AqM7IrnPtOoPX4l6SQ4h0uu5j9HuTbZQSLcaxWieiOp4Nl9WoQ9rMAMtiMv4FJsd+ln+188NQg5dNFswHeApYZeyMgIIYYTOCDXEZx4McJXZDxLKAjUsanaqn9+VN1dmx90IJAYYddqR3xSHJ7/6rTI0Rvy4S1UF5eHxn5zEqubpN6PRqbOZNFeIxMXlyuBoUN7CNnehw7IIkymec6+e+6bzh5/DRQXadr9i7B3MawEEGdQ1M3usmEXfg4oQByYZGjsBXAw9rhOao9pXKDmHpLUkdNrKe0t28a/NHHcQ5cMiaqNmt15hcMGl/VPIsdI89BKA2ZVN2mS+awrQBXTkdYhVuT02gp7Fsbfmpj2xYYN745C+nY2zWFq6irfj2eqSStSmEgMnHQcE5jKsn6ZYr8s5b+TKXs2zCzYsWLLFIRf9RYgLngjmW6UwSGIPlbkSFEdjpkSYMjAFnvbzpKu8/n706qafurRm43/FkPcpgmSrMM2rb6zAzMxJD9+N6s0+cv0riB0D76g/33ihXbipMFeM+UlIsAKyMQM75fGWkP0gix7syfJhogzRCxk6QjnCaZXjhTy26xB7mDJOvTr0+/l715lTYAvm3uihI8VsnCGAKN8F2FRNLQs6CfL3SxjHMUzrtM0lxkN7/wZhK6Ycd/vPaa8TgHIxu2nHzys9/BJQ3YYgDDqZbTyiYp+8JmpZp3ppWD38aa+IvvwPtlfWv/bafPxR2792pdrcuU/C2az31wFwg+GEg+HixxIAh4+ICNs5jtAqXXNjNlzuBfKa6ZcMIMIg3r1IVXu/ovMeJ3CM5LkZVmLXfiB06pEUB0MP/bAd7R9cTgLqIPBGHxxvIJKkhona5OuOT2DF2DoLIQgM9DhB5LrFy3W9HVMGWc1scUURzb2SYNUyQ9AUbONn+A0L4yizpzJWjsLoW1IAKzMCMM/ejNZZepEbpZMDYmb0P3f2OslKGO/Vwe9zYPX83RFMEaI3Uky+yGhodzt7fZCUpRZugucO5t98YhiAgJQHdXbOLBDQaQ6BBZcmz2Oze65Tn2cxXKx/7s7FK7p15lCIAKRF2lQ+2FWU73ZWFFARIYT+hBGcYMejBDd9YI2+hZMtoF+TlVWSICkAPmEd39baziJYlJXSHdm64a2L7IXfQ9wODcDiQXom1gkfU8rO06SQIH04OJgRyQE4wMDQ90orEkwgybiliHBMizssK10D8e+VfgXR51TlJkrIgFG6190aNIg0tBEPIeHJaNRz3bEx5QMRrDjfvfaZGNi8cibDHGW3I29YUcG8gKcHVN+2nbY+Evo3cYDTJ1LU2aowA2UMQSTVZYNkrMiBoa66qpNjZgZcN/9NyU4ustvMQSXLNkzgGHL8rhfSJcr+5HVvKspKDOg7GgdmJEl/dlic7dyUEYi29Lty7tlHkPr8zjXLNnkidHiA1n7mwJ906bsPy+hM3BQVMNuD0a0ZkA0SDPVNZqHpLJcNp4yML9A5jHlnbrbtZcMwcKSKxF+he0gmYKHHFxcX076qNflJdNmMlWFkRHGkMWGADdCn2IPy1f8X5V5jMtI4y1HRluysR0AQFWnueW3OFW2bklx2+xSTxtmZnNWVSL9ZwkdiGxDzAXuf3XrYaZDVj/1qheRvhpk7t629NDGVfuUsm++5HFXbqQWTsMV5r+YMrFdTe3CIHaT1JULHnoNGbyrNjHDVPH4AVWeDG0ScEGWPwtIOrms2v7CZrjayCLKQpNtGOX3pn4I3RpyMZuW7UYWuoFhAJmXZawJ2ydYVJZ51FoyMUTB4CtQFhD2hpGyV5U7QO7ELA5+sOrLYoZYowOSbN5gO1r2rNR+zCyB/eBNfIPW7XKJFQzNBVe/IdyrpfjCs60gFxcHvJkhF+yIWmOJjUm3PV8BTlap8Pdnm9AW9dSy/UBbPSydec3Lsit36AyM0llRDBQDJDMFKxPiq5vLknxh14ueVNaUjI39g7MAQ4TKEhl6/v4U1HBbpypq2FDNjHnYCmPKCIaEyhomzgREZoZ+57fbEfu9yOR9YQVhJUcXWn9O3gJl3jBxMfrUM0tsjD8fx7picnhIgOgvtzNb2mRL1OPa/jlDRmF2Gz55Vgs4yc4Yk7K4FjDUDXI5GUMYbXfiyJ7WiEgS2AjM7wHidRpc1wmMzJI9DOvRK6ms8KgwAeHsKPnb7/1z9fXHMdmX0qEl71hfij6Pe0wyhW5sSYwZPvDvByWOy6FmkKIEqpKSt2UskRtu5KXX8YWmSkZ2mArztsGjeoMhtJ+uJB9eXdUu7/eY8j+XLJrwTn7BDPvmTwDixEgGHSbyUWV5yzvssEsRZI/ulf34f0nJtK/C3wKC4XpOdVMH4EJV56qr+ulm2PdMGsxb75nx1b7S70km3KzCptLbN0CElsV/AZCb+cStaLxKAAAAAElFTkSuQmCC);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\">\n</picture>\n<figcaption>Exemple de page Rubygems</figcaption>\n</figure>\n<h2 id=\"les-themes-distants-sur-github-pages\">Les thèmes distants sur GitHub Pages</h2>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">plugins:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">jekyll-remote-theme</span>\n\n<span class=\"hljs-attr\">remote_theme:</span> <span class=\"hljs-string\">daviddarnes/alembic</span></code></pre>\n<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>\n<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>\n<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>\n<h2 id=\"tests-et-mises-a-jour\">Tests et mises à jour</h2>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/10/03/interview-hugo-lead-developer/",
      "url": "https://jamstatic.fr/2017/10/03/interview-hugo-lead-developer/",
      "title": "Questions à Bjørn Erik Pedersen, le développeur d’Hugo",
      "summary": "Bjørn Erik Pedersen répond à nos questions sur Hugo, le gestionnaire de sites statiques ultra-rapide.\n",
      "date_published": "2017-10-03T11:45:36+00:00","content_text": "Hugo est rapidement devenu l’un des gestionnaires de sites statiques les plus populaires comme en attestent ses bientôt 50 000 étoiles sur GitHub. 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.\nHeureusement ce GSS propose aussi tout un tas de fonctionnalités comme les contenus imbriqués, les fichiers partiels, les shortcodes, la gestion de l’i18n, 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, Bjørn Erik Pedersen dirige les développements, il a gentiment accepté de répondre à nos questions.\n\n\n\n\n\n\nBjørn Erik Pedersen\n\nBonjour Bjørn Erik, comment t'es-tu retrouvé impliqué dans Hugo ?\nJ'ai passé un dimanche à migrer mon blog de WordPress à Jekyll, 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.\nJ'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 Pull Requests, motivé en partie par l’apprentissage d’un nouveau langage mais également encouragé par Steve Francia, qui a créé les premières versions d’Hugo. Il est très bon pour motiver les gens à contribuer à un projet open source.\nLes problèmes résolus par Hugo\nHugo est une excellente façon de créer et de publier de nombreux contenus 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.\nComme 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 sections imbriquées et les contenus relatifs : structurez bien votre contenu et trouvez-le facilement.\nNous 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’UTF-8. 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.\nComment fait Hugo pour aller si vite ?\nJe 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.\nJ'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.\nLes 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.\nQuels sont les sites les plus visibles qui utilisent Hugo ?\n\n\n\n\n\n\nCapture d'écran du site web labs.usa.gov\n\nParmi les sites que je connais et que j'aime bien il y a labs.usa.gov, netlify.com, cdnplanet.com, support.balsamiq.com, crossref.org, 1password.com, borisfx.com, Urban Airship Documentation.\nIl y en a plein d’autres qui arrivent. Par exemple, Smashing Magazine a annoncé qu'ils étaient en train de travailler sur une refonte entièrement basée sur Hugo.\n\n\n\n\n\n\nLa nouvelle version de Smashing Magazine\n\nComment se porte le projet actuellement ?\nNous 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 brew sous macOS. Et le site gohugo.io encaisse un trafic élevé.\nHugo est open source. Comment se passe la gestion du projet ?\nJe 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.\nQuand 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.\nLe forum d’Hugo 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.\nQui vous soutient dans le développement ?\nNetlify héberge nos sites gratuitement et Discourse se charge de la maintenance du forum. Travis, Appveyor et CircleCI 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.\nQuelle est la prochaine étape pour Hugo ?\nLa version 1.0 d’Hugo ne devrait 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 \"pages bundles\" : la possibilité d’associer des contenus comme une page et des images par exemple.\nJ'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.",
      "content_html": "<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>\nHeureusement 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>\n<figure>\n<picture title=\"Bjørn Erik Pedersen\">\n<source type=\"image/webp\" srcset=\"/assets/images/hugo/hugo-bjorn-erik-pedersen.31cf0d1e1e259f4d2121d777f80c0f94.webp\" width=\"640\" height=\"429\">\n<source type=\"image/avif\" srcset=\"/assets/images/hugo/hugo-bjorn-erik-pedersen.31cf0d1e1e259f4d2121d777f80c0f94.avif\" width=\"640\" height=\"429\">\n<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;\">\n</picture>\n<figcaption><a href=\"https://github.com/bep\" target=\"_blank\" rel=\"noopener noreferrer\">Bjørn Erik Pedersen</a></figcaption>\n</figure>\n<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>\n<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>\n<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>\n<h2 id=\"les-problemes-resolus-par-hugo\">Les problèmes résolus par Hugo</h2>\n<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>\n<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>\n<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>\n<h2 id=\"comment-fait-hugo-pour-aller-si-vite\">Comment fait Hugo pour aller si vite ?</h2>\n<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>\n<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>\n<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>\n<h2 id=\"quels-sont-les-sites-les-plus-visibles-qui-utilisent-hugo\">Quels sont les sites les plus visibles qui utilisent Hugo ?</h2>\n<figure>\n<picture title=\"Capture d&#039;écran du site web labs.usa.gov\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Capture d'écran du site web labs.usa.gov</figcaption>\n</figure>\n<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>\n<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>\n<figure>\n<picture title=\"La nouvelle version de Smashing Magazine\">\n<source type=\"image/webp\" srcset=\"/assets/images/algolia-smashing.6641a8e16893175dbc69416f2b1c2a9c.webp\" width=\"720\" height=\"450\">\n<source type=\"image/avif\" srcset=\"/assets/images/algolia-smashing.6641a8e16893175dbc69416f2b1c2a9c.avif\" width=\"720\" height=\"450\">\n<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,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAHrklEQVR4nOVbbZbjKAws2Z7L7An2/pfYy8TW/gCBJATBjnuSzOg9twlfBhUlCeym//79h3FFiNKFfKcFWNyV6xBSPUKujpwof1W3o0e+kG8nyWE+P7tz+sPM6Qcf6TrcvZTLNZiUk+0iGvValhiMZQFp0LR6qCYYseIiJfu6z9r058ZndGQ7M4om8JGfegBY8p2P4dNHsiXYJ4Xywymni/LXml5Xx5AFhUUyJ8eSRtFEZjqqZZCvS319X9JXU6sGbpOZIYUFxwEsB7ATQHsCo3RGUadP5TxDjHlaKwCrpFfQqlhTQIEDhZTiKgCk2FPqmhq1uAJgIeiZpwiNGAfdxqWZgYOriTp2YKEEyk4A9lxH9zOv5W26ZhGq7FgVENtWACkgkfUlSbGFHmCtSpVnsko+4KoWYc2Vxv7FyuDOD4a3+Vz7EYZon7HvwP6oK6mwCADxafN1kiEKDM2QbQO2X6B1BdbNmi0FCCDMsOarXeNQALbCMhQZk863o4Uxc+Fkuf41Ht5VNr5DgbEsiSGUKctQzj4Pkqn38EZOMISq7/AM2bZ6CSCFJZRAUA6egfTbqIXMY0y+cl1Rm4YZpVwSA/celbHzVoUh6aLjAB8HsO7A46HAECDWBBgdWV/zkdY8Q/SsRbmUWEJLZsb2q5ouBUgBRaKtYv+pdKceUpkR5FvAVL4enh63WvotUzwLXE32BZ4hKZJMRRmIZa+MKRHmvJz3IWbv4dmirmUBZ0Co+JAKCFANFSvFS1KUb3NhwKzNek498Aemgou+onxVQMiMYsqkYIAX4FiAXUyXNdGoLTEjBZArUXMP+7LC/fzBaZxZ2wxWYyUQaUNBIGlPKrcgleuSDS8baIIwqsWonX3r3J2LaVrMFo5lniGiZc6eK+9YiZM9pX0vjKj12TIJyamLkLE/pPYn+Teq6TLMUdKEvMYBTWgmMmnyq1lQyPPO4e6+g/d0TyFwNmfi/O1DpqQBZOD+DBhpAHlgxyNvjrIajwO87DX01RQugKidBSnz5X6bcZFngDZfAUtCfz1Y9lEtdjnMyaHnufPjkRz74wHad2APjk9OyEkfosAgSgPatb3MK0T2I+L4jU2luuxNz9QwQdSszXEvFBZprXW0zJtZITJviTzan+QFeQgomR0Cyr7nBSrnWhz2O5KtN9Z44egog/KAoByGlO0tIIAFRe5Gu9ZMFSvpdhxsWnRGzLYs8hIxg3wVtmlGniNX5QsYchcTxkfXR/XkfJQlDAElMICsPAYgoV8O/zwgJlryUYh6BMi6IrRp18AnHFMmnLtq7ttVneqT3qQHkg2isMWA8aLJmmKKPEBWCQHYxZmt6bDN+w8Jd4v0wdD7lAgINq1Cr9v1C+1P7pWacLekWRX6IxQNjDdZJ+TCWRYsS2Q6+eHMC8CUYnN92gvHElZp6CJrhIr54ogdWvkeCHH3sHWanwEorOqxD5OzAdWgmMPG6+wAOoBMM0XbUzAIBxgLwEtiSmFHPu31oIQGKHbZJeL2Y5DRBWbL1ok6EEdd27HqhwOAS7J5CRW8mLog1xhSBiagAOADzJTNGKPuygnpLLpntgKTJSnO/iTbezK6fQaGzSfnoHWi9CV+wjS3fknXM8DgNSBEhoA8ZUoeEB2uznKkN2lEyXxFJqsLTjU1yUzlPKNfXceucOtXIpMmfUUgOJ/RE+1LSj+vASHyGkOAOjlZLYdihn+Fa46hPQhNXFSOSaR/413UCjarPwQkuEMzY+Az0HLY9H+zTAEyZIooSt6SLW6xNJSi6XnYgz1udZAdbAWMC0gjIKCA0Ky7X73n5XWGwDK3AKPPsQxDdMOmp7hvJeWIpNwcGAKSWf01XXiod+Cdp49OBH5KTr0xHNaVQgFjQbVEo064V2AriQkzjpYrQKm8miJhLXtWBE/7BGaI3MIQAO2sdmSWSCHZS77MaFBzHbFOcu1uBAQ4BwQtC0ZD/gS59D6kW1esgwmk2LGkqQBTIey8MsK8MSnOmOu7E/DQ1HwiCFpeZshIfzZ44hw2xZEW+bZNf87IsHSvmHJlAh8mJ96HtDJVtzHWmjEVIImlQkcf+JmnB45fKrf5kFOmQFWmZuVHh+xxF38SECJdQG5nSqdhEOgOm/yJIGi5L8rKMgvOVcX+qcwQeQrITzPlpwH8NrklyurtdCOZ8xDxc/4GUKYBeaZAnqgTbQOf9f03gKDlMkO4c90trzDjE86mzsppQGaYMhJq6rQ5knu3+O3oJ8rtDOkBYvfnvY8c7NnTSHFnjkc+GQAvt4e9wAgU2QZmFelvfUqdUQ/XlPsNzBBZnlcZy4gpPgKrYLj360Tl8xIDWOd5s2P5Rrn16GSkDPv1bcrRB4/U8SXSdxSZPTvP+iZmiLzMkFnprvzGapk350Ef8b7nCjPoyfUOuY0h3fXdHJOLeUoHic1/1+aT31mFRCz4RmaI3OrU+0YnSQGGAfnPnaJ81kCc9wCvgPBJwL0MiD8K6R2NVJakzxHMR2+AYspr7vjbj1huNVnAzMZRfbpjdiTvj4s+AcjLgPSimsHnCkG994PwafKjPuTsigs+eXiLvPP5b2HIs37fDcg75TQgo0gqYsjsO5J3x/+RvOXLxVc76EVZknd1Up8EzO+U/wEMOtmaP0G0/wAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;\">\n</picture>\n<figcaption>La nouvelle version de <a href=\"https://www.smashingmagazine.com\" target=\"_blank\" rel=\"noopener noreferrer\">Smashing Magazine</a></figcaption>\n</figure>\n<h2 id=\"comment-se-porte-le-projet-actuellement\">Comment se porte le projet actuellement ?</h2>\n<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>\n<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>\n<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>\n<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>\nLe <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>\n<h2 id=\"qui-vous-soutient-dans-le-developpement\">Qui vous soutient dans le développement ?</h2>\n<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>\n<h2 id=\"quelle-est-la-prochaine-etape-pour-hugo\">Quelle est la prochaine étape pour Hugo ?</h2>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/10/01/templates-hugo-designers-wordpress/",
      "url": "https://jamstatic.fr/2017/10/01/templates-hugo-designers-wordpress/",
      "title": "Les templates Hugo pour les designers WordPress",
      "summary": "Découvrons les bases du générateur de site Hugo en le comparant à son ancêtre gbien connu WordPress.",
      "date_published": "2017-10-01T11:45:36+00:00","content_text": "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 site de support et de documentation de Balsamiq, Leon Barnard 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.\nL'année dernière j'avais écris un article générique d’introduction aux concepts qui se cachent derrière les gestionnaires de site statique. Cette fois j'aimerais aborder quelques spécificités de base d’un des générateurs les plus populaires appelé Hugo, en le comparant à son illustre ancêtre WordPress.\nIntroduction\nAvant de commencer, je préfère préciser que je n'aborderai pas la migration de WordPress vers Hugo. Un outil d’export est mis à disposition et de nombreux billets de blog décrivent comment d’autres ont migré.\nJe 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.\nLa 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.\nQuand 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.\n\n\n\n\n\n\nÉdition d’un modèle de page pour Hugo.\n\nFondamentaux des templates Hugo\nLa 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 un aperçu du langage Go.\nSi l’idée d’apprendre un nouveau langage de programmation représente pour vous un no-Go (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 documentation d’Hugo.\nLaissez-moi vous convaincre en vous donnant vos premières leçons pour créer des modèles pour Hugo.\nUne syntaxe différente, des fonctionnalités similaires\nDans WordPress, quand vous souhaitez afficher le contenu d’un article de blog ou d’une page, vous écrivez cette ligne dans un modèle:\n&lt;!--?php the_content(); ?--&gt;\nDans Hugo c'est :\n{{ .Content }}\nPas 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.)\nContinuons, en mentionnant au passage que, comme en PHP, on peut mélanger le HTML et le langage de templating d’Hugo.\nSi vous avez déjà personnalisé un modèle WordPress, vous avez probablement écrit ce genre de mélange d’HTML et de PHP:\n&lt;a href=\"\/\"&gt;&lt;?php bloginfo('name'); ?&gt;&lt;\/a&gt;\nCe code localise le nom du site et crée un lien vers la page d’accueil. WordPress dispose d’une fonction appelée bloginfo à qui on passe le paramètre name pour le récupérer.\nHugo, de son côté, utilise une variable nommée .Site avec une propriété appelée .Title. Le même code dans Hugo s'écrit ainsi:\n&lt;a href=\"\/\"&gt;{{ .Site.Title }}&lt;\/a&gt;\nLà encore c'est également assez intuitif, non ?\nJ'espère que vous vous sentez plus serein maintenant. 🙂\nContinuons sur notre lancée.\nLe 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.\nDans WordPress…\n&lt;ul&gt;\n    &lt;?php while (have_posts()) : the_post(); ?&gt;\n        &lt;li&gt;&lt;a href=\"&lt;?php the_permalink(); ?&gt;\"&gt;&lt;?php the_title(); ?&gt;&lt;\/a&gt;&lt;\/li&gt;\n    &lt;?php endwhile; ?&gt;\n&lt;\/ul&gt;\nWordPress utilise une boucle PHP while, qu'on retrouve dans beaucoup de langages de programmation. Hugo utilise une simple fonction appelée range qui a le même objectif:\n&lt;ul&gt;\n    {{ range .Data.Pages }}\n        &lt;li&gt;&lt;a href=\"{{ .Permalink }}\"&gt;{{ .Title }}&lt;\/a&gt;&lt;\/li&gt;\n    {{ end }}\n&lt;\/ul&gt;\nLes é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.\nPrenons de la hauteur\nBon, maintenant que nous avons vu quelques-unes des différences syntaxiques entre WordPress et Hugo, voyons maintenant les différents types de modèles.\nLes includes s'appellent des partials\nUne 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.\nLa fonction include() PHP est, à elle seule, une des raisons pour laquelle le langage est devenu si populaire dans le Web et pourquoi WordPress utilise PHP.\nVous pouvez utiliser la fonction include() 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 :\n&lt;?php get_footer(); ?&gt;\nHugo n'émet aucune hypothèse quant à la structure de votre page comme le fait WordPress. Hugo utilise une fonction similaire à la fonction include() de PHP appelée partial pour insérer le contenu d’un fichier dans un autre.\nVoilà comment ça marche :\n{{ partial \"footer.html\" . }}\nN'oubliez pas le point (.) à la fin, il désigne le contexte.\nNotez que le nom du fichier partiel doit se trouver dans le répertoire partials, un des sous-dossiers du dossier layouts où sont stockés les fichiers des modèles.\nC’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 (comme on peut le voir sur cette image).\nAlors que les sites statiques sont simplement des copies de fichiers sur votre ordinateur, ce qui vous permet de visualiser et de manipuler la structure de votre site.\n\n\n\n\n\n\nUn exemple typique du dossier layouts d’Hugo\n\nC’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.\nIci, 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.\nHeureusement, Hugo fait preuve de consistence dans le nommage des fichiers. C’est bien pratique que la fonction s'appelle partial, c'est une bonne manière de se rappeler de placer vos fichiers partiels dans le dossier partials. 🙂\nLes modèles de section\nPour 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.\n\n\n\n\n\n\nArticles et pages dans WordPress\n\n\n\n\n\n\n\nRépertoires de contenu dans Hugo.\n\nL'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.\nLa documentation d’Hugo explique que \"bien qu'Hugo supporte l’imbrication de contenu à tous les niveaux, les dossiers de premier niveau sont spéciaux\". Pour renforcer cette idée, les dossiers de premier niveau possèdent une nomenclature à part entière dans Hugo, ils désignent une section de contenu, ou section 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.\nPour vous, cela signifie qu'Hugo facilite la création de différents modèles pour chaque dossier(\"section\") de contenu.\nen termes d’arborescence de fichiers, nous passons du dossier partials au dossier section situé lui aussi dans le dossier layouts d’Hugo. Une fois de plus, la nomenclature des dossiers est importante.\nCela 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.\nLe 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.\nPour voir à quoi cela ressemble en pratique, jetons un œil à la structure du site de support de Balsamiq qui possède, entre autres, des sections appelées “plugins”, “tutorials”, “sales”.\n\n\n\n\n\n\nLe dossier content du site de support de Balsamiq\n\nDans le dossier section, 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\").\n\n\n\n\n\n\nLe dossier section du site de support de Balsamiq\n\nChaque 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.\nLes shortcodes\nTerminons avec une fonctionnalité sur laquelle Hugo et WordPress sont entièrement d’accord. Hugo dispose d’une fonctionnalité familière bien utile appelée… shortcodes, bien connue dans WordPress sous le nom de… shortcodes. 🙂\nUn shortcode est un petit bout de texte, une sorte de raccourci pour une portion de code plus importante.\nDans WordPress, vous écrivez un shortcode en l’entourant de crochets et en lui passant des paramètres comme ceci:\n[gallery id=\"123\" size=\"medium\"]\nCela 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 :\n{{ &lt;gallery id=\"123\" size=\"medium\"&gt; }}\nDans Hugo, chaque shortcode est un fichier de modèle HTML. Et où devez-vous ranger ces fichiers ? Dans un dossier nommé shortcodes évidemment, vous vous en doutiez.\nÉ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 {{ .Get 0 }} - mais écrire des shortcodes pour WordPress n'est pas non plus une partie de plaisir.\nEn pratique, vous allez vraisemblablement utiliser (ou peut-être personnaliser) un shortcode existant, comme vous le faites dans WordPress. Hugo fournit quelques shortcodes bien pratiques par défaut pour YouTube, Instagram, Twitter, etc.\nSur le site de documentation de Balsamiq, nous utilisons par exemple les shortcodes pour les messages d’alerte et d’information, comme on peut le voir ici :\n\n\n\n\n\n\nLes messages d’information et d’alerte dans la documentation de Balsamiq.\n\nCe thème pour Hugo présente quelques exemples sympas de ce qu'on peut faire avec des shortcodes en pratique.\nLa fin et le début\nDans 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.\nBien entendu, je suis loin d’avoir tout couvert. Hugo support aussi les tags comme WordPress à l’aide des taxonomies et les modèles de contenu par défaut avec les archetypes, mais vous n'avez pas besoin d’être tout de suite (voire jamais) familier avec ces notions.\nUne chose que j'ai remarquée dans l’évolution d’Hugo est qu'il devient moins ésotérique et beaucoup plus pratique 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é.\nJe me rappelle par exemple avoir passé des journées à essayer de comprendre cette explication des taxonomies au début. Alors que des fonctionnalités plus récentes comme les sections sont beaucoup plus simples à appréhender.\nEnfin, 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.\n\nLa documentation officielle d’Hugo.\nLe forum officiel d’Hugo – un bon endroit pour trouver des réponses à vos questions et déboguer votre code.\nLes tutoriels vidéo de Giraffe Academy sur YouTube - 23 vidéos !\nLe thème “Learn” pour Hugo - Un bon endroit pour commencer à jouer.\nD'autres thèmes pour Hugo - pour que vous n'ayez pas à commencer de zéro.\nUn des nombreux billets de blog sur le passage de WordPress à Hugo.\nBien démarrer avec les sites statiques - mon article précédent sur les sites statiques.\n\nMerci de m'avoir lu. Bonne génération de site !",
      "content_html": "<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>\n<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>\n<h2 id=\"introduction\">Introduction</h2>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"Édition d’un modèle de page pour Hugo.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Édition d’un modèle de page pour Hugo.</figcaption>\n</figure>\n<h2 id=\"fondamentaux-des-templates-hugo\">Fondamentaux des templates Hugo</h2>\n<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>\n<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>\n<p>Laissez-moi vous convaincre en vous donnant vos premières leçons pour créer des modèles pour Hugo.</p>\n<h3 id=\"une-syntaxe-differente-des-fonctionnalites-similaires\">Une syntaxe différente, des fonctionnalités similaires</h3>\n<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>\n<pre><code class=\"language-php hljs php\">&lt;!--?php the_content(); ?--&gt;</code></pre>\n<p>Dans Hugo c'est :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ .Content }}</code></pre>\n<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>\n<p>Continuons, en mentionnant au passage que, comme en PHP, on peut mélanger le HTML et le langage de templating d’Hugo.</p>\n<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>\n<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>\n<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>\n<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>\n<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>\n<p>Là encore c'est également assez intuitif, non ?</p>\n<p>J'espère que vous vous sentez plus serein maintenant. 🙂</p>\n<p>Continuons sur notre lancée.</p>\n<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>\n<p>Dans WordPress…</p>\n<pre><code class=\"language-php hljs php\">&lt;ul&gt;\n    <span class=\"hljs-meta\">&lt;?php</span> <span class=\"hljs-keyword\">while</span> (have_posts()) : the_post(); <span class=\"hljs-meta\">?&gt;</span>\n        &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;\n    <span class=\"hljs-meta\">&lt;?php</span> <span class=\"hljs-keyword\">endwhile</span>; <span class=\"hljs-meta\">?&gt;</span>\n&lt;/ul&gt;</code></pre>\n<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>\n<pre><code class=\"language-go-html-template hljs go\">&lt;ul&gt;\n    {{ <span class=\"hljs-keyword\">range</span> .Data.Pages }}\n        &lt;li&gt;&lt;a href=<span class=\"hljs-string\">\"{{ .Permalink }}\"</span>&gt;{{ .Title }}&lt;/a&gt;&lt;/li&gt;\n    {{ end }}\n&lt;/ul&gt;</code></pre>\n<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>\n<h2 id=\"prenons-de-la-hauteur\">Prenons de la hauteur</h2>\n<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>\n<h3 id=\"les-includes-s-appellent-des-partials\">Les includes s'appellent des <code>partials</code></h3>\n<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>\n<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>\n<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>\n<pre><code class=\"language-php hljs php\"><span class=\"hljs-meta\">&lt;?php</span> get_footer(); <span class=\"hljs-meta\">?&gt;</span></code></pre>\n<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>\n<p>Voilà comment ça marche :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"footer.html\"</span> . }}</code></pre>\n<aside class=\"note note-tip\"><p>N'oubliez pas le point (<code>.</code>) à la fin, il désigne le contexte.</p></aside>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"Un exemple typique du dossier layouts d’Hugo\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Un exemple typique du dossier <code>layouts</code> d’Hugo</figcaption>\n</figure>\n<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>\n<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>\n<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>\n<h3 id=\"les-modeles-de-section\">Les modèles de section</h3>\n<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>\n<figure>\n<picture title=\"Articles et pages dans WordPress\">\n<source type=\"image/webp\" srcset=\"/images/2017-10-01_templates-hugo-designers-wordpress/pages.629618fdd484c6be082f83ede1f29763.webp\" width=\"280\" height=\"262\">\n<source type=\"image/avif\" srcset=\"/images/2017-10-01_templates-hugo-designers-wordpress/pages.629618fdd484c6be082f83ede1f29763.avif\" width=\"280\" height=\"262\">\n<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;\">\n</picture>\n<figcaption>Articles et pages dans WordPress</figcaption>\n</figure>\n<figure>\n<picture title=\"Répertoires de contenu dans Hugo.\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Répertoires de contenu dans Hugo.</figcaption>\n</figure>\n<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>\n<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>\n<p>Pour vous, cela signifie qu'Hugo facilite la création de différents modèles pour chaque dossier(\"section\") de contenu.</p>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"Le dossier content du site de support de Balsamiq\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Le dossier content du site de support de Balsamiq</figcaption>\n</figure>\n<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>\n<figure>\n<picture title=\"Le dossier section du site de support de Balsamiq\">\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n</figure>\n<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>\n<h3 id=\"les-shortcodes\">Les shortcodes</h3>\n<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>\n<p>Un <code>shortcode</code> est un petit bout de texte, une sorte de raccourci pour une portion de code plus importante.</p>\n<p>Dans WordPress, vous écrivez un <code>shortcode</code> en l’entourant de crochets et en lui passant des paramètres comme ceci:</p>\n<pre><code class=\"language-html hljs xml\">[gallery id=\"123\" size=\"medium\"]</code></pre>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"Les messages d’information et d’alerte dans la documentation de Balsamiq.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<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>\n</figure>\n<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>\n<h2 id=\"la-fin-et-le-debut\">La fin et le début</h2>\n<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>\n<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>\n<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>\n<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>\n<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>\n<ul>\n<li><a href=\"https://gohugo.io/documentation/\" target=\"_blank\" rel=\"noopener noreferrer\">La documentation officielle d’Hugo</a>.</li>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n</ul>\n<p>Merci de m'avoir lu. Bonne génération de site !</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/09/02/bibliotheques-de-composants-en-markdown/",
      "url": "https://jamstatic.fr/2017/09/02/bibliotheques-de-composants-en-markdown/",
      "title": "Des bibliothèques de composants avec Shadow DOM en Markdown",
      "summary": "Apprenez à inclure facilement des démos de composants dans du Markdown, à l’aide des shortcodes de Hugo et de l’encapsulation Shadow DOM.",
      "date_published": "2017-09-02T00:00:00+00:00","content_text": "S'il est un domaine où les générateurs de site statique se sont\nrapidement imposés c'est bien celui des sites de documentation. Avec la\nnécessaire rationalisation des interfaces multi-supports, beaucoup d’équipes en\ncharge du front-end doivent aujourd’hui maintenir une bibliothèque de composants\nqui sert de référence à toutes les équipes et qui permet donc d’assurer la\ncohérence et l’évolutivité des composants dans les diverses sites et\napplications web de l’entreprise. Un composant comme un formulaire de recherche\nassocie du code HTML, du CSS et du JavaScript et il n'est pas forcément aisé\nd’isoler le comportement de vos composants quand vous les insérez en tant\nqu'exemples dans une documentation. Heydon Pickering, consultant UX et\naccessibilité chez Paciello Group et auteur du livre\nInclusive Design Patterns\ns'est dit qu'il pouvait tirer parti des fonctionnalités de templating et de\nmodularisation du générateur Hugo pour mener à bien cette tâche, il nous\nexplique tout cela en détail. Une bonne occasion pour découvrir les\nfonctionnalités liées aux snippets de code dans Hugo et d’apprendre à créer des\néléments Shadow DOM en JavaScript.\nIl y a des gens qui détestent écrire de la documentation et d’autres qui\ndétestent écrire tout court. Il se trouve que j'aime écrire, sinon vous ne\nseriez pas en train de lire ceci. Ça tombe bien, car en tant que consultant en\nDesign qui fournit un suivi professionnel, écrire représente une part importante\nde mon travail. Par contre je déteste, mais alors je déteste les traitements de\ntexte.\nMon workflow habituel de travail avec un traitement de texte ressemble un peu à ça :\n\nSélectionner du texte que je veux copier dans une autre partie du document,\nS'apercevoir que l’application en a sélectionné un peu plus que ce que je lui avais demandé,\nEssayer 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,\nCopier-coller la sélection,\nS'apercevoir que le formatage du texte collé est quelque peu différent de l’orignal,\nTenter de trouver le style prédéfini qui correspond au texte d’origine,\nEssayer d’appliquer le style,\nLaisser tomber et appliquer la police de caractère et la taille à la main,\nS'apercevoir qu'il y a un espace trop important au-dessus du texte collé, et appuyer sur “Backspace” pour le supprimer,\nS'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,\nRéfléchir au sens de la vie.\n\nLorsque vous devez écrire de la documentation pour le web (comprenez\ndes bibliothèques de composants)\nles traitements de texte ne sont pas simplement désobéissants, mais totalement\ninadaptés. Idéalement je voudrais pouvoir écrire de manière à pouvoir inclure\nles composants que je documente dans le flux du texte, et cela n'est possible\nque si la documentation elle-même est écrite en HTML en CSS et en JavaScript.\nDans cet article, je vais vous montrer comme inclure facilement des démos de\ncode dans des documents Markdown avec l’aide de snippets et de l’encapsulation\ndu Shadow DOM.\n\n\n\n\n\n\nUn M, une flèche qui pointe vers le bas et un détective caché dans l’obscurité pour symboliser Markdown et Shadow Dom\n\nCSS et Markdown\nOn pourra dire ce qu'on veut sur CSS, c'est un outil de composition certainement\nplus consistent et plus sûr qu'un éditeur WYSIWYG ou qu'un traitement de texte.\nPourquoi ? Parce qu'il n'y a pas de boîte noire renfermant des algorithmes de\nhaut-niveau qui essaient de deviner où les styles doivent vraiment\ns'appliquer. Au contraire, c'est beaucoup plus explicite : vous définissez les\ncirconstances dans lesquelles les styles s'appliquent aux éléments)\net ces règles sont respectées.\nLe seul problème avec CSS c'est que ça vous demande d’écrire du HTML en\ncontre-partie. Même les amoureux du HTML voudront bien concéder que c'est\npénible lorsque vous souhaitez simplement écrire de la prose. C’est là où\nMarkdown entre en jeu. Avec sa syntaxe concise et son jeu de fonctionnalités\nréduit à l’essentiel, il offre une façon d’écrire qui est simple à apprendre et\nqui peut tirer parti — une fois converti automatiquement en HTML — des\nfonctionnalités de composition puissantes et prédictives de CSS. Ce n'est pas\npar hasard qu'il est devenu le format de facto des générateurs de site\nstatique et des plate-formes moderne de blog comme Ghost.\nPour des besoins plus complexes, quand un balisage spécifique est requis, la\nmajorité des parseurs Markdown vous permettent d’écrire directement du HTML.\nToutefois, plus il y a de balisages complexes, moins vote système de création\nest accessible aux personnes moins techniques, ou à ceux qui n'ont pas beaucoup\nde temps et de patience. C’est là où les snippets de code rentrent en jeu.\nLes shortcodes d’Hugo\nHugo est un générateur de site statique écrit en Go — un\nlangage polyvalent compilé développé par Google. Grâce à la parallélisation (et\nsans doute, à d’autres fonctionnalités bas-niveau que je ne comprends pas\nvraiment), Go permet à Hugo d’être un générateur de contenu statique\nultra-rapide. C’est l’une des nombreuses raisons pour lesquelles Hugo a été\nsélectionné pour la nouvelle version du site de Smashing Magazine.\nEn dehors de la performance, il fonctionne de la même manière que les\ngénérateurs en Ruby ou en NodeJS avec lesquels vous êtes peut-être déjà\nfamiliers : du Markdown et des méta-données (en YAML, TOML ou JSON) transformés\nà l’aide de modèles. Sara Soueidan a écrit une excellente introduction aux\nprincipales fonctionnalités d’Hugo.\nPour moi la fonctionnalité qui tue dans Hugo c'est son\nimplémentation des snippets de code. Les\nhabitués de WordPress seront peut-être déjà familiers avec ce concept : un\nraccourci syntaxique destiné principalement à insérer des bouts de codes\ncomplexes ou issus de services tiers. Par example WordPress inclus un raccourci\npour Vimeo qui prend juste l’ID de la vidéo Vimeo en question.\n&lt;iframe src=\"https:\/\/player.vimeo.com\/video\/207263942?h=4a34bbf60a\" width=\"640\" height=\"360\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen&gt;&lt;\/iframe&gt;\n&lt;p&gt;&lt;a href=\"https:\/\/vimeo.com\/207263942\"&gt;Hugo in the garden&lt;\/a&gt; from &lt;a href=\"https:\/\/vimeo.com\/twistedpoly\"&gt;Twistedpoly&lt;\/a&gt; on &lt;a href=\"https:\/\/vimeo.com\"&gt;Vimeo&lt;\/a&gt;.&lt;\/p&gt;\nLes crochets indiquent que le contenu doit être traité et remplacé par le\nbalisage HTML complet lorsque le contenu est parsé.\nÀ l’aide des fonctions de templating de Go, Hugo fourni une API très simple pour\ncréer des shortcodes personnalisés. Par exemple, j’ai créé un shortcode\nCodePen très simple que peux inclure dans mon contenu en Markdown :\nUn peu de contenu en Markdown avant le shortcode. Aliquam sodales rhoncus dui,\nsed congue velit semper ut. Class aptent taciti sociosqu ad litora torquent.\n\n&lt;p class=\"codepen\" data-height=\"300\" data-default-tab=\"html,result\" data-slug-hash=\"VpVNKW\" data-user=\"heydon\" style=\"height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;\"&gt;\n  &lt;span&gt;See the Pen &lt;a href=\"https:\/\/codepen.io\/heydon\/pen\/VpVNKW\"&gt;\n  Vue.js TODO List &lt;\/a&gt; by Heydon (&lt;a href=\"https:\/\/codepen.io\/heydon\"&gt;@heydon&lt;\/a&gt;)\n  on &lt;a href=\"https:\/\/codepen.io\"&gt;CodePen&lt;\/a&gt;.&lt;\/span&gt;\n&lt;\/p&gt;\n&lt;script async src=\"https:\/\/cpwebassets.codepen.io\/assets\/embed\/ei.js\"&gt;&lt;\/script&gt;\n\nUn peu de contenu en Markdown après le shortcode. Nulla vel magna sit amet dui\nlobortis commodo vitae vel nulla sit amet ante hendrerit tempus.\nHugo recherche automatiquement le modèle nommé codePen.html dans le\nsous-dossier shortcodes pour pouvoir parser le shortcode pendant l’étape de\ncompilation. Mon implémentation ressemble à ça :\n{{ if .Site.Params.codePenUser }}\n  &lt;iframe height='300' scrolling='no' title=\"démonstration CodePen\" src='\/\/codepen.io\/{{ .Site.Params.codepenUser | lower }}\/embed\/{{ .Get 0 }}\/?height=265&amp;theme-id=dark&amp;default-tab=result,result&amp;embed-version=2' frameborder='no' allowtransparency='true' allowfullscreen='true' style='width: 100%;'&gt;\n    &lt;div&gt;\n      &lt;a href=\"\/\/codepen.io\/{{ .Site.Params.codePenUser | lower }}\/pen\/{{ .Get 0 }}\"&gt;Voir la démo sur CodePen&lt;\/a&gt;\n    &lt;\/div&gt;\n  &lt;\/iframe&gt;\n{{ else }}\n  &lt;p class=\"site-error\"&gt;&lt;strong&gt;Attention :&lt;\/strong&gt; Le paramètre &lt;code&gt;codePenUser&lt;\/code&gt; n'a pas été renseigné dans le fichier &lt;code&gt;config.toml&lt;\/code&gt;&lt;\/p&gt;\n{{ end }}\nPour vous faire une meilleure idée de la manière dont fonctionne le langage de\ntemplating de Go, vous devrez consulter\nl’introduction à Go Template\nd’Hugo. En attendant, retenez simplement cela :\n\nC’est pas super sexy mais c'est très puissant.\n{{ .Get 0 }} 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.\nLe . référence le contexte actuel. Donc .Get 0 signifie “Récupère le premier argument fourni pour le shortcode courant.”\n\nQuoi qu'il en soit, je pense que les shortcodes sont la meilleure chose qui\nexiste depuis le pain de mie en tranches et l’implémentation d’Hugo pour écrire\ndes shortcodes personnalisés est vraiment impressionnante. Je me dois aussi de\nmentionner qu'il est possible d’utiliser les\nincludes de Jekyll pour parvenir à un\nrésultat similaire, mais je les trouve moins souples et moins puissants.\nDémos de code sans tierce partie\n\nJ'aime beaucoup CodePen (et toutes les autres aires disponibles pour jouer avec\ndu code), mais on se heurte à des problèmes inhérents à ces plate-formes\nlorsqu'on veut inclure ces extraits de code dans une bibliothèque de composants\n\n\n\nOn dépend d’une API et il n'est pas toujours facile de faire fonctionner cela efficacement en mode hors-ligne.\nCela ne représente pas un simple composant, c'est une interface complexe à part entière qui embarque les styles du service utilisé.\nCela crée du bruit et une distraction inutile alors que le focus devrait être sur le composant.\n\nPendant un temps, j'ai tenté d’inclure mes démos de composants en utilisant mes\npropres iframes. Je faisais pointer l’iframe vers un fichier en local qui\ncontenait la page web de la démo. Grâce aux iframes, j'étais capable\nd’encapsuler les styles et le comportement des éléments sans me reposer sur une\ntierce partie.\nMalheureusement, les iframes sont difficiles à manier et à redimensionner\ndynamiquement. en termes de complexité de création, cela demande de maintenir\ndes fichiers distincts et de créer des liens. Je préfèrerais pouvoir écrire mes\ncomposants in situ et inclure juste le code nécessaire pour les faire\nfonctionner. Je voudrais pouvoir écrire des démos comme j'écris de la\ndocumentation.\nLe shortcode demo\nHeureusement, Hugo vous permet de créer des shortcodes qui incluent du contenu\nentre des balises de shortcode ouvrantes et fermantes. Ce contenu est\ndisponible dans le fichier shortcode en utilisant {{ .Inner }}. Imaginons\ndonc que je veuille utiliser un shortcode demo de la façon suivante :\n{{&lt;\/* demo *\/&gt;}}\n  C’est le contenu !\n{{&lt;\/* \/demo *\/&gt;}}\n“C’est le contenu !” serait accessible via {{ .Inner }} dans le fichier de\nmodèle demo.html qui va le parser. C’est un bon point de départ pour insérer\ndes démos de code en ligne, mais il reste encore le problème de l’encapsulation.\nEncapsulation du style\nIl y a trois choses dont il faut se soucier pour l’encapsulation des styles :\n\nles styles hérités de la page parente par le composant,\nles styles hérités du composant par la page parente,\nles styles partagés involontairement entre les composants.\n\nUne solution consiste à bien cibler les sélecteurs CSS de manière à ce qu'ils ne\ns'appliquent à différents composants ou aux composants et aux pages. Cela\nvoudrait dire utiliser des sélecteurs ésotériques pour chaque composant, et ce\nn'est pas une possibilité que j'ai envie de considérer, alors que je pourrais\nécrire du code concis et lisible. Un des avantages des iframes est que les\nstyles sont encapsulés par défaut, donc je pourrais écrire\nbutton { background: blue } et être sûr que ce ne sera appliqué qu'à\nl’intérieur de l’iframe.\nUne manière plus simple d’empêcher l’héritage de styles entre composants dans la\npage est d’utiliser la propriété all avec la valeur initial sur l’élément\nparent de son choix. Je peux définir cet élément dans le fichier demo.html :\n&lt;div class=\"demo\"&gt;\n    {{ .Inner }}\n&lt;\/div&gt;\nEnsuite, je dois appliquer la règle all: initial à toutes les instances de cet\nélément pour qu'elle se propage aux éléments enfants de chaque instance.\n.demo {\n  all: initial;\n}\nLe comportement de initial est assez… particulier. En pratique, tous les\néléments affectés sont censés retrouver les styles par défaut du user agent\n(comme display: block pour les éléments &lt;h2&gt;). Toutefois, l’élément auquel\nil est appliqué — class=\"demo\" — a besoin qu'on redéfinisse explicitement\ncertains des styles de l’agent utilisateur. Dans notre cas, c'est juste\ndisplay: block, puisque class=\"demo\" est un &lt;div&gt;.\n.demo {\n  all: initial;\n  display: block;\n}\nRemarque : all n'est pour l’instant pas supporté par Microsoft Edge mais\nc'est en considération. À part ça le support est\nassurément large. Pour nos besoins, la\nvaleur revert aurait été plus robuste et plus sûre, mais elle n'est pas encore\nsupportée.\nFaire du shortcode un ShadowDOM\nL'utilisation de all: initial ne met pas nos composants en ligne totalement à\nl’abri de toute influence externe (la spécificité s'applique toujours) mais nous\npouvons raisonnablement estimer que les styles sont désactivés puisque nous\nn'utilisons que la classe demo. Les styles hérités de sélecteurs génériques\ncomme html et body seront généralement exclus.\nToutefois, cela ne règle que le problème des styles issus de l’élément parent\nsur les composants. Pour empêcher les styles écrits pour les composants\nd’affecter d’autres parties de la page, nous allons avoir besoin de\nshadow DOM afin\nde créer une sous-arborescence encapsulée.\nImaginez que je veuille documenter un élément &lt;button&gt; stylé. J'aimerais\npouvoir écrire quelque chose d’aussi simple que l’exemple qui suit, sans\ncraindre que le sélecteur de l’élément button ne s'applique aussi aux éléments\nbutton de la bibliothèque de composants elle-même ou à d’autres composants\nprésents sur la même page.\n{{&lt;\/* demo *\/&gt;}}\n&lt;button&gt;Mon bouton&lt;\/button&gt;\n&lt;style&gt;\nbutton {\n    background: blue;\n    padding: 0.5rem 1rem;\n    text-transform: uppercase;\n}\n&lt;\/style&gt;\n{{&lt;\/* \/demo *\/&gt;}}\nL'astuce est d’utiliser la variable {{ .Inner }} du modèle de shortcode et\nde l’inclure en tant qu'innerHTML d’un nouveau ShadowRoot. On pourrait\nl’implémenter de cette façon :\n{{ $uniq := .Inner | htmlEscape | base64Encode | truncate 15 \"\" }}\n&lt;div class=\"demo\" id=\"demo-{{ $uniq }}\"&gt;&lt;\/div&gt;\n&lt;script&gt;\n    (function() {\n        var root = document.getElementById('demo-{{ $uniq }}');\n        root.attachShadow({mode: 'open'});\n        root.innerHTML = '{{ .Inner }}';\n    })();\n&lt;\/script&gt;\n\n$uniq 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.\nroot.attachShadow fait du conteneur du composant un hôte shadow DOM.\nJe peuple le innerHTML du ShadowRoot avec {{ .Inner }}, qui inclus le CSS maintenant encapsulé.\n\nAutoriser le comportement avec JavaScript\nJ'aimerais aussi inclure des comportements JavaScript dans mes composants. Au\ndébut je pensais que ce serait facile, malheureusement le code JavaScript inséré\nvia innerHTML n'est ni parsé ni exécuté. On peut résoudre ce problème en\nimportant le contenu d’un élément &lt;template&gt;. J'ai corrigé mon implémentation\nen conséquence.\n{{ $uniq := .Inner | htmlEscape | base64Encode | truncate 15 \"\" }}\n&lt;div class=\"demo\" id=\"demo-{{ $uniq }}\"&gt;&lt;\/div&gt;\n&lt;template id=\"template-{{ $uniq }}\"&gt;\n    {{ .Inner }}\n&lt;\/template&gt;\n&lt;script&gt;\n    (function() {\n        var root = document.getElementById('demo-{{ $uniq }}');\n        root.attachShadow({mode: 'open'});\n        var template = document.getElementById('template-{{ $uniq }}');\n        root.shadowRoot.appendChild(document.importNode(template.content, true));\n    })();\n&lt;\/script&gt;\nMaintenant, je peux inclure la démo d’un bouton interrupteur par exemple:\n{{&lt;\/* demo *\/&gt;}}\n&lt;button&gt;Mon bouton&lt;\/button&gt;\n&lt;style&gt;\nbutton {\n    background: blue;\n    padding: 0.5rem 1rem;\n    text-transform: uppercase;\n}\n\n[aria-pressed=\"true\"] {\n    box-shadow: inset 0 0 5px #000;\n}\n&lt;\/style&gt;\n&lt;script&gt;\nvar toggle = document.querySelector('[aria-pressed]');\n\ntoggle.addEventListener('click', (e) =&gt; {\n  let pressed = e.target.getAttribute('aria-pressed’) === 'true';\n  e.target.setAttribute('aria-pressed’, !pressed);\n});\n&lt;\/script&gt;\n{{&lt;\/* \/demo *\/&gt;}}\nNote : J’ai écrit un article détaillé sur\nl’accessibilité des interrupteurs\npour Inclusive Components.\nL'encapsulation de JavaScript\nJavaScript n'est pas, à ma grande surprise,\nencapsulé automatiquement comme\nCSS l’est dans shadow DOM. C’est-à-dire que, s’il y avait un autre bouton\n[aria-pressed] dans la page parente situé avant l’exemple de ce composant,\nalors document.querySelector ciblerait plutôt celui-là.\nCe dont j'ai besoin c'est d’un équivalent de document qui se limite à la\nsous-arborescence de l’élément demo. C’est possible à faire, mais de manière\nassez verbeuse :\ndocument.getElementById(\"demo-{{ $uniq }}\").shadowRoot;\nJe n'avais pas envie de devoir écrire cette expression à chaque fois que je\ndevais cibler des éléments dans les conteneurs de démo. J'en suis donc venu à\nécrire un hack dans lequel j'assigne cette expression à une variable demo\nlocale et à des scripts préfixés fournis via le shortcode avec cette\nassignation :\nif (script) {\n  script.textContent = `(function() { var demo = document.getElementById(\\'demo-{{ $uniq }}\\').shadowRoot; ${\n    script.textContent\n  } })()`;\n}\nroot.shadowRoot.appendChild(document.importNode(template.content, true));\nGrâce à cela, demo devient l’équivalent de document pour n'importe quel\ncomposant de la sous-arborescence et je peux utiliser demo.querySelector pour\ncibler facilement mon bouton interrupteur :\nvar toggle = demo.querySelector('[aria-pressed]');\nRemarquez que j'ai entouré le contenu du script de la démo avec une fonction\nimmédiatement exécutée (IIFE) de manière à ce que la variable demo — et toutes\nles variables traitées et utilisées pour le composant — n'appartiennent pas au\npérimètre global. Comme ça demo peut être utilisé dans n'importe quel script\nprésent dans un shortcode mais se réfèrera uniquement au shortcode en cours.\nLorsque ECMAScript6 est disponible, il est possible de parvenir à localiser la\nportée à l’aide du\n\"block scoping\"\n, en entourant les déclarations let ou const de simples accolades.\nToutefois, toutes les autres définitions de variables à l’intérieur du block\nseraient obligées d’utiliser également let et const (et d’éviter var).\n{\n  let demo = document.getElementById(\"demo-{{ $uniq }}\").shadowRoot;\n  \/\/ Author script injected here\n}\nSupport de Shadow DOM\nBien tout ce qui précède n'est possible que si la version 1 de Shadow DOM est\nsupportée. Tout a l’air de bien marcher dans Chrome, Safari, Opera et Android,\nmais c'est plus problématique avec Firefox ou les navigateurs de Microsoft. On\npeut toujours détecter le support de cette fonctionnalité et fournir un message\nd’erreur lorsque attachShadow n'est pas disponible :\nif (document.head.attachShadow) {\n  \/\/ Do shadow DOM stuff here\n} else {\n  root.innerHTML =\n    \"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.\";\n}\nOu alors vous pouvez inclure Shady DOM et l’extension Shady CSS, ce qui veut\ndire ajouter une dépendance non négligeable (+60KB) et une API différente. Rob\nDodson a été assez gentil pour me fournir une\ndémo basique,\nque je suis ravi de vous partager pour vous aider à vous lancer.\nDes légendes pour les composants\nMaintenant que notre petite démo de base fonctionne, écrire des démos et les\ninsérer dans la documentation est super simple, pour notre plus grand bonheur.\nCela permet de nous poser des questions comme : \"Et si je veux ajouter une\nlégende pour identifier la démo ?\" C’est parfaitement faisable puisque — comme\nnous l’avons déjà vu auparavant — le Markdown permet d’embarquer du code HTML.\n&lt;figure role=\"group\" aria-labelledby=\"legende-bouton\"&gt;\n    {{&lt;\/* demo *\/&gt;}}\n    &lt;button&gt;Mon bouton&lt;\/button&gt;\n    &lt;style&gt;\n    button {\n        background: blue;\n        padding: 0.5rem 1rem;\n        text-transform: uppercase;\n    }\n    &lt;\/style&gt;\n    {{&lt;\/* \/demo *\/&gt;}}\n    &lt;figcaption id=\"legende-bouton\"&gt;Un bouton standard&lt;\/figcaption&gt;\n&lt;\/figure&gt;\nToutefois, la seule nouveauté apportée par cette modification est la formulation\nde la légende. Il serait préférable de fournir une interface de saisie simple,\npour faire gagner du temps à mon futur moi — et à tous ceux qui utiliseront le\nshortcode — et minimiser par la même occasion les possibles erreurs de saisie.\nC’est faisable en fournir un paramètre nommé au shortcode — ici simplement\nappelé legende :\n{{&lt;\/* demo legende=\"Un bouton standard\" *\/&gt;}}\n    … le contenu de la démo …\n{{&lt;\/* \/demo *\/&gt;}}\nIl peut ensuite assez simple d’accéder au paramètre nommé dans le modèle en\nutilisant {{ .Get \"legende\" }}. Je peux rentre optionnel l’insertion des\nbalises &lt;figure&gt; et &lt;figcaption&gt; en insérant une condition, comme ça je\nn'affiche la légende que si elle est passée en argument du shortcode :\n{{ if .Get \"legende\" }}\n    &lt;figcaption&gt;{{ .Get \"legende\" }}&lt;\/figcaption&gt;\n{{ end }}\nVoici maintenant à quoi ressemble notre modèle demo.html (le code n'est pas\ntrès élégant, mais ça fait le job) :\n{{ $uniq := .Inner | htmlEscape | base64Encode | truncate 15 \"\" }}\n{{ if .Get \"legende\" }}\n&lt;figure role=\"group\" aria-labelledby=\"legende-{{ $uniq }}\"&gt;\n{{ end }}\n    &lt;div class=\"demo\" id=\"demo-{{ $uniq }}\"&gt;&lt;\/div&gt;\n    {{ if .Get \"legende\" }}\n        &lt;figcaption id=\"legende-{{ $uniq }}\"&gt;{{ .Get \"legende\" }}&lt;\/figcaption&gt;\n    {{ end }}\n{{ if .Get \"legende\" }}\n&lt;\/figure&gt;\n{{ end }}\n&lt;template id=\"template-{{ $uniq }}\"&gt;\n    {{ .Inner }}\n&lt;\/template&gt;\n&lt;script&gt;\n  (function() {\n      var root = document.getElementById('demo-{{ $uniq }}');\n      root.attachShadow({mode: 'open'});\n      var template = document.getElementById('template-{{ $uniq }}');\n      var script = template.content.querySelector('script');\n      if (script) {\n          script.textContent = `(function() { var demo = document.getElementById(\\'demo-{{ $uniq }}\\').shadowRoot; ${script.textContent} })()`\n       }\n       root.shadowRoot.appendChild(document.importNode(template.content, true));\n  })();\n&lt;\/script&gt;\nUne dernière remarque : si jamais je veux autoriser la syntaxe Markdown dans la\nlégende, je peux la faire passer dans la fonction markdownify d’Hugo. De cette\nmanière l’auteur peut s'il le souhaite insérer du Markdown (et du HTML).\n{{ .Get \"legende\" | markdownify }}\nConclusion\nDe par ses performances et son lot de super fonctionnalités, je trouve qu'Hugo\nest parfait pour la génération de site statique. Et l’insertion de shortcodes\nest ce que je préfère. Ici j'ai été capable de répondre à un besoin pour de la\ndocumentation, que j'avais depuis un bon moment.\nDe même qu'avec les web components, une bonne partie de la complexité du\nbalisage (souvent pour rendre le code accessible) peut être masquée à\nl’utilisateur grâce aux shortcodes. Ici je pense notamment à l’inclusion de\nrole=\"group\" et de la relation aria-labelledby, qui fournit un meilleur\nsupport pour \"group label\" à la balise &lt;figure&gt; — le genre de choses que\npersonne ne prend plaisir à coder plus d’une fois, surtout quand il faut créer\ndes valeurs d’attribut uniques pour chaque instance.\nJe crois que les shortcodes d’Hugo sont à Markdown et au contenu ce que les\nweb composants sont à HTML et à la fonctionnalité : une manière de rendre\nl’écriture plus facile, plus sûre et plus consistante. Il me tarde de voir\ncomment ce curieux petit coin du Web va évoluer.\nRessources\n\nLa documentation d’Hugo\nPackage Template du langage Go\nLes shortcodes d’Hugo\nall (propriété CSS), Mozilla Developer Network\ninitial (CSS), sur le Mozilla Developer Network\nShadow DOM v1 : Self-Contained Web Components, Eric Bidelman, Web Fundamentals, Google Developers\nIntroduction à l’élément template Eiji Kitamura, WebComponents.org\nLes includes de Jekyll\n",
      "content_html": "<aside class=\"note note-intro\"><p>S'il est un domaine où les générateurs de site statique se sont\nrapidement imposés c'est bien celui des sites de documentation. Avec la\nnécessaire rationalisation des interfaces multi-supports, beaucoup d’équipes en\ncharge du front-end doivent aujourd’hui maintenir une bibliothèque de composants\nqui sert de référence à toutes les équipes et qui permet donc d’assurer la\ncohérence et l’évolutivité des composants dans les diverses sites et\napplications web de l’entreprise. Un composant comme un formulaire de recherche\nassocie du code HTML, du CSS et du JavaScript et il n'est pas forcément aisé\nd’isoler le comportement de vos composants quand vous les insérez en tant\nqu'exemples dans une documentation. Heydon Pickering, consultant UX et\naccessibilité chez Paciello Group et auteur du livre\n<a href=\"https://shop.smashingmagazine.com/products/inclusive-design-patterns\" target=\"_blank\" rel=\"noopener noreferrer\">Inclusive Design Patterns</a>\ns'est dit qu'il pouvait tirer parti des fonctionnalités de templating et de\nmodularisation du générateur Hugo pour mener à bien cette tâche, il nous\nexplique tout cela en détail. Une bonne occasion pour découvrir les\nfonctionnalités liées aux snippets de code dans Hugo et d’apprendre à créer des\néléments Shadow DOM en JavaScript.</p></aside>\n<p>Il y a des gens qui détestent écrire de la documentation et d’autres qui\ndétestent écrire tout court. Il se trouve que j'aime écrire, sinon vous ne\nseriez pas en train de lire ceci. Ça tombe bien, car en tant que consultant en\nDesign qui fournit un suivi professionnel, écrire représente une part importante\nde mon travail. Par contre je déteste, mais alors je déteste les traitements de\ntexte.</p>\n<p>Mon workflow habituel de travail avec un traitement de texte ressemble un peu à ça :</p>\n<ol>\n<li>Sélectionner du texte que je veux copier dans une autre partie du document,</li>\n<li>S'apercevoir que l’application en a sélectionné un peu plus que ce que je lui avais demandé,</li>\n<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>\n<li>Copier-coller la sélection,</li>\n<li>S'apercevoir que le formatage du texte collé est quelque peu différent de l’orignal,</li>\n<li>Tenter de trouver le style prédéfini qui correspond au texte d’origine,</li>\n<li>Essayer d’appliquer le style,</li>\n<li>Laisser tomber et appliquer la police de caractère et la taille à la main,</li>\n<li>S'apercevoir qu'il y a un espace trop important au-dessus du texte collé, et appuyer sur “Backspace” pour le supprimer,</li>\n<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>\n<li>Réfléchir au sens de la vie.</li>\n</ol>\n<p>Lorsque vous devez écrire de la documentation pour le web (comprenez\n<a href=\"https://www.smashingmagazine.com/taking-pattern-libraries-next-level/\" target=\"_blank\" rel=\"noopener noreferrer\">des bibliothèques de composants</a>)\nles traitements de texte ne sont pas simplement désobéissants, mais totalement\ninadaptés. Idéalement je voudrais pouvoir écrire de manière à pouvoir inclure\nles composants que je documente dans le flux du texte, et cela n'est possible\nque si la documentation elle-même est écrite en HTML en CSS et en JavaScript.\nDans cet article, je vais vous montrer comme inclure facilement des démos de\ncode dans des documents Markdown avec l’aide de snippets et de l’encapsulation\ndu Shadow DOM.</p>\n<figure>\n<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\">\n<source type=\"image/webp\" srcset=\"/images/2017-09-02_bibliotheques-de-composants-en-markdown/markdown-shadowdom-preview-opt.104c8bc81466d61bbb2e313b88aa0be8.webp\" width=\"499\" height=\"221\">\n<source type=\"image/avif\" srcset=\"/images/2017-09-02_bibliotheques-de-composants-en-markdown/markdown-shadowdom-preview-opt.104c8bc81466d61bbb2e313b88aa0be8.avif\" width=\"499\" height=\"221\">\n<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;\">\n</picture>\n<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>\n</figure>\n<h3 id=\"css-et-markdown\">CSS et Markdown</h3>\n<p>On pourra dire ce qu'on veut sur CSS, c'est un outil de composition certainement\nplus consistent et plus sûr qu'un éditeur WYSIWYG ou qu'un traitement de texte.\nPourquoi ? Parce qu'il n'y a pas de boîte noire renfermant des algorithmes de\nhaut-niveau qui essaient de deviner où les styles doivent <em>vraiment</em>\ns'appliquer. Au contraire, c'est beaucoup plus explicite : vous définissez les\n<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>)\net ces règles sont respectées.</p>\n<p>Le seul problème avec CSS c'est que ça vous demande d’écrire du HTML en\ncontre-partie. Même les amoureux du HTML voudront bien concéder que c'est\npénible lorsque vous souhaitez simplement écrire de la prose. C’est là où\nMarkdown entre en jeu. Avec sa syntaxe concise et son jeu de fonctionnalités\nréduit à l’essentiel, il offre une façon d’écrire qui est simple à apprendre et\nqui peut tirer parti — une fois converti automatiquement en HTML — des\nfonctionnalités de composition puissantes et prédictives de CSS. Ce n'est pas\npar hasard qu'il est devenu le format <em>de facto</em> des générateurs de site\nstatique et des plate-formes moderne de blog comme Ghost.</p>\n<p>Pour des besoins plus complexes, quand un balisage spécifique est requis, la\nmajorité des parseurs Markdown vous permettent d’écrire directement du HTML.\nToutefois, plus il y a de balisages complexes, moins vote système de création\nest accessible aux personnes moins techniques, ou à ceux qui n'ont pas beaucoup\nde temps et de patience. C’est là où les snippets de code rentrent en jeu.</p>\n<h3 id=\"les-shortcodes-d-hugo\">Les <em>shortcodes</em> d’Hugo</h3>\n<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\nlangage polyvalent compilé développé par Google. Grâce à la parallélisation (et\nsans doute, à d’autres fonctionnalités bas-niveau que je ne comprends pas\nvraiment), Go permet à Hugo d’être un générateur de contenu statique\nultra-rapide. C’est l’une des nombreuses raisons pour lesquelles Hugo a été\nsélectionné pour la nouvelle version du site de Smashing Magazine.</p>\n<p>En dehors de la performance, il fonctionne de la même manière que les\ngénérateurs en Ruby ou en NodeJS avec lesquels vous êtes peut-être déjà\nfamiliers : du Markdown et des méta-données (en YAML, TOML ou JSON) transformés\nà l’aide de modèles. Sara Soueidan a écrit une <a href=\"/2017/06/07/migration-de-jekyll-a-hugo/\">excellente introduction aux\nprincipales fonctionnalités d’Hugo</a>.</p>\n<p>Pour moi la fonctionnalité qui tue dans Hugo c'est son\n<a href=\"https://gohugo.io/extras/shortcodes/\" target=\"_blank\" rel=\"noopener noreferrer\">implémentation des snippets de code</a>. Les\nhabitués de WordPress seront peut-être déjà familiers avec ce concept : un\nraccourci syntaxique destiné principalement à insérer des bouts de codes\ncomplexes ou issus de services tiers. Par example WordPress inclus un raccourci\npour Vimeo qui prend juste l’ID de la vidéo Vimeo en question.</p>\n<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>\n<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>\n<p>Les crochets indiquent que le contenu doit être traité et remplacé par le\nbalisage HTML complet lorsque le contenu est parsé.</p>\n<p>À l’aide des fonctions de templating de Go, Hugo fourni une API très simple pour\ncréer des <em>shortcodes</em> personnalisés. Par exemple, j’ai créé un <em>shortcode</em>\nCodePen très simple que peux inclure dans mon contenu en Markdown :</p>\n<pre><code class=\"language-html hljs xml\">Un peu de contenu en Markdown avant le shortcode. Aliquam sodales rhoncus dui,\nsed congue velit semper ut. Class aptent taciti sociosqu ad litora torquent.\n\n<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>\n  <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>\n  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>)\n  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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">p</span>&gt;</span>\n<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>\n\nUn peu de contenu en Markdown après le shortcode. Nulla vel magna sit amet dui\nlobortis commodo vitae vel nulla sit amet ante hendrerit tempus.</code></pre>\n<p>Hugo recherche automatiquement le modèle nommé <code>codePen.html</code> dans le\nsous-dossier <code>shortcodes</code> pour pouvoir parser le <em>shortcode</em> pendant l’étape de\ncompilation. Mon implémentation ressemble à ça :</p>\n<pre><code class=\"language-html hljs xml\">{{ if .Site.Params.codePenUser }}\n  <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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div</span>&gt;</span>\n      <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>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">iframe</span>&gt;</span>\n{{ else }}\n  <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>\n{{ end }}</code></pre>\n<p>Pour vous faire une meilleure idée de la manière dont fonctionne le langage de\ntemplating de Go, vous devrez consulter\n<a href=\"https://gohugo.io/templates/go-templates/\" target=\"_blank\" rel=\"noopener noreferrer\">l’introduction à Go Template</a>\nd’Hugo. En attendant, retenez simplement cela :</p>\n<ul>\n<li>C’est pas super sexy mais c'est très puissant.</li>\n<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>\n<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>\n</ul>\n<p>Quoi qu'il en soit, je pense que les <em>shortcodes</em> sont la meilleure chose qui\nexiste depuis le pain de mie en tranches et l’implémentation d’Hugo pour écrire\ndes <em>shortcodes</em> personnalisés est vraiment impressionnante. Je me dois aussi de\nmentionner qu'il est possible d’utiliser les\n<a href=\"https://jekyllrb.com/docs/includes/\" target=\"_blank\" rel=\"noopener noreferrer\">includes de Jekyll</a> pour parvenir à un\nrésultat similaire, mais je les trouve moins souples et moins puissants.</p>\n<h3 id=\"demos-de-code-sans-tierce-partie\">Démos de code sans tierce partie</h3>\n<dl>\n<dt>J'aime beaucoup CodePen (et toutes les autres aires disponibles pour jouer avec</dt>\n<dt>du code), mais on se heurte à des problèmes inhérents à ces plate-formes</dt>\n<dt>lorsqu'on veut inclure ces extraits de code dans une bibliothèque de composants</dt>\n<dd></dd>\n</dl>\n<ul>\n<li>On dépend d’une API et il n'est pas toujours facile de faire fonctionner cela efficacement en mode hors-ligne.</li>\n<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>\n<li>Cela crée du bruit et une distraction inutile alors que le focus devrait être sur le composant.</li>\n</ul>\n<p>Pendant un temps, j'ai tenté d’inclure mes démos de composants en utilisant mes\npropres iframes. Je faisais pointer l’iframe vers un fichier en local qui\ncontenait la page web de la démo. Grâce aux iframes, j'étais capable\nd’encapsuler les styles et le comportement des éléments sans me reposer sur une\ntierce partie.</p>\n<p>Malheureusement, les iframes sont difficiles à manier et à redimensionner\ndynamiquement. en termes de complexité de création, cela demande de maintenir\ndes fichiers distincts et de créer des liens. Je préfèrerais pouvoir écrire mes\ncomposants <em>in situ</em> et inclure juste le code nécessaire pour les faire\nfonctionner. Je voudrais pouvoir écrire des démos comme j'écris de la\ndocumentation.</p>\n<h3 id=\"le-shortcode-demo\">Le <em>shortcode</em> <code>demo</code></h3>\n<p>Heureusement, Hugo vous permet de créer des <em>shortcodes</em> qui incluent du contenu\nentre des balises de <em>shortcode</em> ouvrantes et fermantes. Ce contenu est\ndisponible dans le fichier <em>shortcode</em> en utilisant <code>{{ .Inner }}</code>. Imaginons\ndonc que je veuille utiliser un <em>shortcode</em> <code>demo</code> de la façon suivante :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{&lt;/* demo */&gt;}}\n  C’est le contenu !\n{{&lt;/* /demo */&gt;}}</code></pre>\n<p>“C’est le contenu !” serait accessible via <code>{{ .Inner }}</code> dans le fichier de\nmodèle <code>demo.html</code> qui va le parser. C’est un bon point de départ pour insérer\ndes démos de code en ligne, mais il reste encore le problème de l’encapsulation.</p>\n<h4 id=\"encapsulation-du-style\">Encapsulation du style</h4>\n<p>Il y a trois choses dont il faut se soucier pour l’encapsulation des styles :</p>\n<ul>\n<li>les styles hérités de la page parente par le composant,</li>\n<li>les styles hérités du composant par la page parente,</li>\n<li>les styles partagés involontairement entre les composants.</li>\n</ul>\n<p>Une solution consiste à bien cibler les sélecteurs CSS de manière à ce qu'ils ne\ns'appliquent à différents composants ou aux composants et aux pages. Cela\nvoudrait dire utiliser des sélecteurs ésotériques pour chaque composant, et ce\nn'est pas une possibilité que j'ai envie de considérer, alors que je pourrais\nécrire du code concis et lisible. Un des avantages des iframes est que les\nstyles sont encapsulés par défaut, donc je pourrais écrire\n<code>button { background: blue }</code> et être sûr que ce ne sera appliqué qu'à\nl’intérieur de l’iframe.</p>\n<p>Une manière plus simple d’empêcher l’héritage de styles entre composants dans la\npage est d’utiliser la propriété <code>all</code> avec la valeur <code>initial</code> sur l’élément\nparent de son choix. Je peux définir cet élément dans le fichier <code>demo.html</code> :</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;div class=<span class=\"hljs-string\">\"demo\"</span>&gt;\n    {{ .Inner }}\n&lt;/div&gt;</code></pre>\n<p>Ensuite, je dois appliquer la règle <code>all: initial</code> à toutes les instances de cet\nélément pour qu'elle se propage aux éléments enfants de chaque instance.</p>\n<pre><code class=\"language-css hljs css\"><span class=\"hljs-selector-class\">.demo</span> {\n  <span class=\"hljs-attribute\">all</span>: initial;\n}</code></pre>\n<p>Le comportement de <code>initial</code> est assez… particulier. En pratique, tous les\néléments affectés sont censés retrouver les styles par défaut du user agent\n(comme <code>display: block</code> pour les éléments <code>&lt;h2&gt;</code>). Toutefois, l’élément auquel\nil est appliqué — <code>class=\"demo\"</code> — a besoin qu'on redéfinisse explicitement\ncertains des styles de l’agent utilisateur. Dans notre cas, c'est juste\n<code>display: block</code>, puisque <code>class=\"demo\"</code> est un <code>&lt;div&gt;</code>.</p>\n<pre><code class=\"language-css hljs css\"><span class=\"hljs-selector-class\">.demo</span> {\n  <span class=\"hljs-attribute\">all</span>: initial;\n  <span class=\"hljs-attribute\">display</span>: block;\n}</code></pre>\n<p><strong>Remarque</strong> : <code>all</code> n'est pour l’instant pas supporté par Microsoft Edge mais\nc'est en considération. À part ça le support est\n<a href=\"https://caniuse.com/css-all\" target=\"_blank\" rel=\"noopener noreferrer\">assurément large</a>. Pour nos besoins, la\nvaleur <code>revert</code> aurait été plus robuste et plus sûre, mais elle n'est pas encore\nsupportée.</p>\n<h4 id=\"faire-du-shortcode-un-shadowdom\">Faire du shortcode un ShadowDOM</h4>\n<p>L'utilisation de <code>all: initial</code> ne met pas nos composants en ligne totalement à\nl’abri de toute influence externe (la spécificité s'applique toujours) mais nous\npouvons raisonnablement estimer que les styles sont désactivés puisque nous\nn'utilisons que la classe <code>demo</code>. Les styles hérités de sélecteurs génériques\ncomme <code>html</code> et <code>body</code> seront généralement exclus.</p>\n<p>Toutefois, cela ne règle que le problème des styles issus de l’élément parent\nsur les composants. Pour empêcher les styles écrits pour les composants\nd’affecter d’autres parties de la page, nous allons avoir besoin de\n<a href=\"https://glazkov.com/2011/01/14/what-the-heck-is-shadow-dom/\" target=\"_blank\" rel=\"noopener noreferrer\">shadow DOM</a> afin\nde créer une sous-arborescence encapsulée.</p>\n<p>Imaginez que je veuille documenter un élément <code>&lt;button&gt;</code> stylé. J'aimerais\npouvoir écrire quelque chose d’aussi simple que l’exemple qui suit, sans\ncraindre que le sélecteur de l’élément <code>button</code> ne s'applique aussi aux éléments\n<code>button</code> de la bibliothèque de composants elle-même ou à d’autres composants\nprésents sur la même page.</p>\n<pre><code class=\"language-go-html-template hljs go\">{{&lt;/* demo */&gt;}}\n&lt;button&gt;Mon bouton&lt;/button&gt;\n&lt;style&gt;\nbutton {\n    background: blue;\n    padding: <span class=\"hljs-number\">0.5</span>rem <span class=\"hljs-number\">1</span>rem;\n    text-transform: uppercase;\n}\n&lt;/style&gt;\n{{&lt;/* /demo */&gt;}}</code></pre>\n<p>L'astuce est d’utiliser la variable <code>{{ .Inner }}</code> du modèle de <em>shortcode</em> et\nde l’inclure en tant qu'<code>innerHTML</code> d’un nouveau <code>ShadowRoot</code>. On pourrait\nl’implémenter de cette façon :</p>\n<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> }}\n&lt;div class=<span class=\"hljs-string\">\"demo\"</span> id=<span class=\"hljs-string\">\"demo-{{ $uniq }}\"</span>&gt;&lt;/div&gt;\n&lt;script&gt;\n    (function() {\n        <span class=\"hljs-keyword\">var</span> root = document.getElementById(<span class=\"hljs-string\">'demo-{{ $uniq }}'</span>);\n        root.attachShadow({mode: <span class=\"hljs-string\">'open'</span>});\n        root.innerHTML = <span class=\"hljs-string\">'{{ .Inner }}'</span>;\n    })();\n&lt;/script&gt;</code></pre>\n<ul>\n<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>\n<li><code>root.attachShadow</code> fait du conteneur du composant un hôte shadow DOM.</li>\n<li>Je peuple le <code>innerHTML</code> du <code>ShadowRoot</code> avec <code>{{ .Inner }}</code>, qui inclus le CSS maintenant encapsulé.</li>\n</ul>\n<h4 id=\"autoriser-le-comportement-avec-javascript\">Autoriser le comportement avec JavaScript</h4>\n<p>J'aimerais aussi inclure des comportements JavaScript dans mes composants. Au\ndébut je pensais que ce serait facile, malheureusement le code JavaScript inséré\nvia <code>innerHTML</code> n'est ni parsé ni exécuté. On peut résoudre ce problème en\nimportant le contenu d’un élément <code>&lt;template&gt;</code>. J'ai corrigé mon implémentation\nen conséquence.</p>\n<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> }}\n&lt;div class=<span class=\"hljs-string\">\"demo\"</span> id=<span class=\"hljs-string\">\"demo-{{ $uniq }}\"</span>&gt;&lt;/div&gt;\n&lt;template id=<span class=\"hljs-string\">\"template-{{ $uniq }}\"</span>&gt;\n    {{ .Inner }}\n&lt;/template&gt;\n&lt;script&gt;\n    (function() {\n        <span class=\"hljs-keyword\">var</span> root = document.getElementById(<span class=\"hljs-string\">'demo-{{ $uniq }}'</span>);\n        root.attachShadow({mode: <span class=\"hljs-string\">'open'</span>});\n        <span class=\"hljs-keyword\">var</span> template = document.getElementById(<span class=\"hljs-string\">'template-{{ $uniq }}'</span>);\n        root.shadowRoot.appendChild(document.importNode(template.content, <span class=\"hljs-literal\">true</span>));\n    })();\n&lt;/script&gt;</code></pre>\n<p>Maintenant, je peux inclure la démo d’un bouton interrupteur par exemple:</p>\n<pre><code class=\"language-go-html-template hljs go\">{{&lt;/* demo */&gt;}}\n&lt;button&gt;Mon bouton&lt;/button&gt;\n&lt;style&gt;\nbutton {\n    background: blue;\n    padding: <span class=\"hljs-number\">0.5</span>rem <span class=\"hljs-number\">1</span>rem;\n    text-transform: uppercase;\n}\n\n[aria-pressed=<span class=\"hljs-string\">\"true\"</span>] {\n    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>;\n}\n&lt;/style&gt;\n&lt;script&gt;\n<span class=\"hljs-keyword\">var</span> toggle = document.querySelector(<span class=\"hljs-string\">'[aria-pressed]'</span>);\n\ntoggle.addEventListener(<span class=\"hljs-string\">'click'</span>, (e) =&gt; {\n  let pressed = e.target.getAttribute(<span class=\"hljs-string\">'aria-pressed’) === '</span><span class=\"hljs-literal\">true</span><span class=\"hljs-string\">';\n  e.target.setAttribute('</span>aria-pressed’, !pressed);\n});\n&lt;/script&gt;\n{{&lt;/* /demo */&gt;}}</code></pre>\n<p><strong>Note</strong> : J’ai écrit un article détaillé sur\n<a href=\"https://inclusive-components.design/toggle-button/\" target=\"_blank\" rel=\"noopener noreferrer\">l’accessibilité des interrupteurs</a>\npour Inclusive Components.</p>\n<h4 id=\"l-encapsulation-de-javascript\">L'encapsulation de JavaScript</h4>\n<p>JavaScript n'est pas, à ma grande surprise,\n<a href=\"https://robdodson.me/shadow-dom-javascript/\" target=\"_blank\" rel=\"noopener noreferrer\">encapsulé automatiquement</a> comme\nCSS l’est dans shadow DOM. C’est-à-dire que, s’il y avait un autre bouton\n<code>[aria-pressed]</code> dans la page parente situé avant l’exemple de ce composant,\nalors <code>document.querySelector</code> ciblerait plutôt celui-là.</p>\n<p>Ce dont j'ai besoin c'est d’un équivalent de <code>document</code> qui se limite à la\nsous-arborescence de l’élément <code>demo</code>. C’est possible à faire, mais de manière\nassez verbeuse :</p>\n<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>\n<p>Je n'avais pas envie de devoir écrire cette expression à chaque fois que je\ndevais cibler des éléments dans les conteneurs de démo. J'en suis donc venu à\nécrire un hack dans lequel j'assigne cette expression à une variable <code>demo</code>\nlocale et à des scripts préfixés fournis via le <em>shortcode</em> avec cette\nassignation :</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-keyword\">if</span> (script) {\n  script.textContent = <span class=\"hljs-string\">`(function() { var demo = document.getElementById(\\'demo-{{ $uniq }}\\').shadowRoot; <span class=\"hljs-subst\">${\n    script.textContent\n  }</span> })()`</span>;\n}\nroot.shadowRoot.appendChild(<span class=\"hljs-built_in\">document</span>.importNode(template.content, <span class=\"hljs-literal\">true</span>));</code></pre>\n<p>Grâce à cela, <code>demo</code> devient l’équivalent de <code>document</code> pour n'importe quel\ncomposant de la sous-arborescence et je peux utiliser <code>demo.querySelector</code> pour\ncibler facilement mon bouton interrupteur :</p>\n<pre><code class=\"language-html hljs xml\">var toggle = demo.querySelector('[aria-pressed]');</code></pre>\n<p>Remarquez que j'ai entouré le contenu du script de la démo avec une fonction\nimmédiatement exécutée (IIFE) de manière à ce que la variable <code>demo</code> — et toutes\nles variables traitées et utilisées pour le composant — n'appartiennent pas au\npérimètre global. Comme ça <code>demo</code> peut être utilisé dans n'importe quel script\nprésent dans un <em>shortcode</em> mais se réfèrera uniquement au <em>shortcode</em> en cours.</p>\n<p>Lorsque ECMAScript6 est disponible, il est possible de parvenir à localiser la\nportée à l’aide du\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block\" target=\"_blank\" rel=\"noopener noreferrer\">\"block scoping\"</a>\n, en entourant les déclarations <code>let</code> ou <code>const</code> de simples accolades.\nToutefois, toutes les autres définitions de variables à l’intérieur du block\nseraient obligées d’utiliser également <code>let</code> et <code>const</code> (et d’éviter <code>var</code>).</p>\n<pre><code class=\"language-js hljs javascript\">{\n  <span class=\"hljs-keyword\">let</span> demo = <span class=\"hljs-built_in\">document</span>.getElementById(<span class=\"hljs-string\">\"demo-{{ $uniq }}\"</span>).shadowRoot;\n  <span class=\"hljs-comment\">// Author script injected here</span>\n}</code></pre>\n<h4 id=\"support-de-shadow-dom\">Support de Shadow DOM</h4>\n<p>Bien tout ce qui précède n'est possible que si la version 1 de Shadow DOM est\nsupportée. Tout a l’air de bien marcher dans Chrome, Safari, Opera et Android,\nmais c'est plus problématique avec Firefox ou les navigateurs de Microsoft. On\npeut toujours détecter le support de cette fonctionnalité et fournir un message\nd’erreur lorsque <code>attachShadow</code> n'est pas disponible :</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-keyword\">if</span> (<span class=\"hljs-built_in\">document</span>.head.attachShadow) {\n  <span class=\"hljs-comment\">// Do shadow DOM stuff here</span>\n} <span class=\"hljs-keyword\">else</span> {\n  root.innerHTML =\n    <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>;\n}</code></pre>\n<p>Ou alors vous pouvez inclure Shady DOM et l’extension Shady CSS, ce qui veut\ndire ajouter une dépendance non négligeable (+60KB) et une API différente. Rob\nDodson a été assez gentil pour me fournir une\n<a href=\"https://gist.github.com/robdodson/287030402bad4b496a0361314138f0f9\" target=\"_blank\" rel=\"noopener noreferrer\">démo basique</a>,\nque je suis ravi de vous partager pour vous aider à vous lancer.</p>\n<h3 id=\"des-legendes-pour-les-composants\">Des légendes pour les composants</h3>\n<p>Maintenant que notre petite démo de base fonctionne, écrire des démos et les\ninsérer dans la documentation est super simple, pour notre plus grand bonheur.\nCela permet de nous poser des questions comme : \"Et si je veux ajouter une\nlégende pour identifier la démo ?\" C’est parfaitement faisable puisque — comme\nnous l’avons déjà vu auparavant — le Markdown permet d’embarquer du code HTML.</p>\n<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;\n    {{&lt;/* demo */&gt;}}\n    &lt;button&gt;Mon bouton&lt;/button&gt;\n    &lt;style&gt;\n    button {\n        background: blue;\n        padding: <span class=\"hljs-number\">0.5</span>rem <span class=\"hljs-number\">1</span>rem;\n        text-transform: uppercase;\n    }\n    &lt;/style&gt;\n    {{&lt;/* /demo */&gt;}}\n    &lt;figcaption id=<span class=\"hljs-string\">\"legende-bouton\"</span>&gt;Un bouton standard&lt;/figcaption&gt;\n&lt;/figure&gt;</code></pre>\n<p>Toutefois, la seule nouveauté apportée par cette modification est la formulation\nde la légende. Il serait préférable de fournir une interface de saisie simple,\npour faire gagner du temps à mon futur moi — et à tous ceux qui utiliseront le\n<em>shortcode</em> — et minimiser par la même occasion les possibles erreurs de saisie.\nC’est faisable en fournir un paramètre nommé au <em>shortcode</em> — ici simplement\nappelé <code>legende</code> :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{&lt;/* demo legende=<span class=\"hljs-string\">\"Un bouton standard\"</span> */&gt;}}\n    … le contenu de la démo …\n{{&lt;/* /demo */&gt;}}</code></pre>\n<p>Il peut ensuite assez simple d’accéder au paramètre nommé dans le modèle en\nutilisant <code>{{ .Get \"legende\" }}</code>. Je peux rentre optionnel l’insertion des\nbalises <code>&lt;figure&gt;</code> et <code>&lt;figcaption&gt;</code> en insérant une condition, comme ça je\nn'affiche la légende que si elle est passée en argument du <em>shortcode</em> :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ <span class=\"hljs-keyword\">if</span> .Get <span class=\"hljs-string\">\"legende\"</span> }}\n    &lt;figcaption&gt;{{ .Get <span class=\"hljs-string\">\"legende\"</span> }}&lt;/figcaption&gt;\n{{ end }}</code></pre>\n<p>Voici maintenant à quoi ressemble notre modèle <code>demo.html</code> (le code n'est pas\ntrès élégant, mais ça fait le job) :</p>\n<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> }}\n{{ <span class=\"hljs-keyword\">if</span> .Get <span class=\"hljs-string\">\"legende\"</span> }}\n&lt;figure role=<span class=\"hljs-string\">\"group\"</span> aria-labelledby=<span class=\"hljs-string\">\"legende-{{ $uniq }}\"</span>&gt;\n{{ end }}\n    &lt;div class=<span class=\"hljs-string\">\"demo\"</span> id=<span class=\"hljs-string\">\"demo-{{ $uniq }}\"</span>&gt;&lt;/div&gt;\n    {{ <span class=\"hljs-keyword\">if</span> .Get <span class=\"hljs-string\">\"legende\"</span> }}\n        &lt;figcaption id=<span class=\"hljs-string\">\"legende-{{ $uniq }}\"</span>&gt;{{ .Get <span class=\"hljs-string\">\"legende\"</span> }}&lt;/figcaption&gt;\n    {{ end }}\n{{ <span class=\"hljs-keyword\">if</span> .Get <span class=\"hljs-string\">\"legende\"</span> }}\n&lt;/figure&gt;\n{{ end }}\n&lt;template id=<span class=\"hljs-string\">\"template-{{ $uniq }}\"</span>&gt;\n    {{ .Inner }}\n&lt;/template&gt;\n&lt;script&gt;\n  (function() {\n      <span class=\"hljs-keyword\">var</span> root = document.getElementById(<span class=\"hljs-string\">'demo-{{ $uniq }}'</span>);\n      root.attachShadow({mode: <span class=\"hljs-string\">'open'</span>});\n      <span class=\"hljs-keyword\">var</span> template = document.getElementById(<span class=\"hljs-string\">'template-{{ $uniq }}'</span>);\n      <span class=\"hljs-keyword\">var</span> script = template.content.querySelector(<span class=\"hljs-string\">'script'</span>);\n      <span class=\"hljs-keyword\">if</span> (script) {\n          script.textContent = <span class=\"hljs-string\">`(function() { var demo = document.getElementById(\\'demo-{{ $uniq }}\\').shadowRoot; ${script.textContent} })()`</span>\n       }\n       root.shadowRoot.appendChild(document.importNode(template.content, <span class=\"hljs-literal\">true</span>));\n  })();\n&lt;/script&gt;</code></pre>\n<p>Une dernière remarque : si jamais je veux autoriser la syntaxe Markdown dans la\nlégende, je peux la faire passer dans la fonction <code>markdownify</code> d’Hugo. De cette\nmanière l’auteur peut s'il le souhaite insérer du Markdown (et du HTML).</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ .Get <span class=\"hljs-string\">\"legende\"</span> | markdownify }}</code></pre>\n<h3 id=\"conclusion\">Conclusion</h3>\n<p>De par ses performances et son lot de super fonctionnalités, je trouve qu'Hugo\nest parfait pour la génération de site statique. Et l’insertion de <em>shortcodes</em>\nest ce que je préfère. Ici j'ai été capable de répondre à un besoin pour de la\ndocumentation, que j'avais depuis un bon moment.</p>\n<p>De même qu'avec les web components, une bonne partie de la complexité du\nbalisage (souvent pour rendre le code accessible) peut être masquée à\nl’utilisateur grâce aux <em>shortcodes</em>. Ici je pense notamment à l’inclusion de\n<code>role=\"group\"</code> et de la relation <code>aria-labelledby</code>, qui fournit un meilleur\nsupport pour \"group label\" à la balise <code>&lt;figure&gt;</code> — le genre de choses que\npersonne ne prend plaisir à coder plus d’une fois, surtout quand il faut créer\ndes valeurs d’attribut uniques pour chaque instance.</p>\n<p>Je crois que les <em>shortcodes</em> d’Hugo sont à Markdown et au contenu ce que les\nweb composants sont à HTML et à la fonctionnalité : une manière de rendre\nl’écriture plus facile, plus sûre et plus consistante. Il me tarde de voir\ncomment ce curieux petit coin du Web va évoluer.</p>\n<h4 id=\"ressources\">Ressources</h4>\n<ul>\n<li><a href=\"https://gohugo.io/overview/introduction/\" target=\"_blank\" rel=\"noopener noreferrer\">La documentation d’Hugo</a></li>\n<li><a href=\"https://golang.org/pkg/text/template/\" target=\"_blank\" rel=\"noopener noreferrer\">Package Template du langage Go</a></li>\n<li><a href=\"https://gohugo.io/extras/shortcodes\" target=\"_blank\" rel=\"noopener noreferrer\">Les <em>shortcodes</em> d’Hugo</a></li>\n<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>\n<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>\n<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>\n<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>\n<li><a href=\"https://jekyllrb.com/docs/includes/\" target=\"_blank\" rel=\"noopener noreferrer\">Les includes de Jekyll</a></li>\n</ul>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://snipcart.com/blog/hugo-tutorial-static-site-ecommerce",
      "url": "https://snipcart.com/blog/hugo-tutorial-static-site-ecommerce",
      "title": "Un site ecommerce statique (très performant) avec Hugo",
      "summary": "Un exemple de site de ecommerce statique avec Hugo, Snipcart et Netlify.",
      "date_published": "2017-08-28T12:00:00+00:00","content_text": "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.\nDans cet exemple nous utiliserons le service Snipcart pour la gestion du panier d’achat et Hugo pour générer le site à la vitesse de l’éclair.\n\nPressé ? Passez directement au tutoriel ou à la démo et au code dispo sur GitHub.\n\nIl est temps de nous plonger à nouveau dans le monde en perpétuel mouvement de la Jamstack 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 Middleman et Jekyll ont eu pas mal de succès, alors pourquoi s'arrêter en si bon chemin ?\nMesdames 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 Hugo.\nDans ce tutoriel nous verrons :\n\nComment générer votre site statique avec le générateur de site Hugo,\nComment y intégrer ensuite facilement le panier d’achat de la plate-forme\nSnipcart,\nComment déployer votre site e-commerce sur Netlify.\n\nMais d’abord un petit mot sur l’outil central que nous allons utiliser pour faire ceci.\nHugo : un générateur de site statique super rapide\nLe nom Hugo 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 développeur (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 rapide comme l’éclair.\nÉcrit en Go par Steve Francia alias spf13 et Bjørn Erik Pedersen alias Bep, 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\nrecommandons Netlify 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.\nAujourd’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 nous l’avons déjà fait pour Star Wars.\n\nPsst : Si vous vous demandez encore ce que sont les générateurs de site\nstatique et pourquoi il faut vous y intéresser, jetez-vous sur\nl’intro d’Eduardo Bouças.\n\nTutoriel Hugo : site, produits, modèles et déploiement\n1. Installer Hugo et générer votre nouveau site web statique\nNous 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 documentation pour démarrer avec Hugo, ou juste 2 minutes si vous êtes aussi rapide que Dan Hersam.\nUne fois que vous avez téléchargé Hugo sur GitHub, l’installation est très rapide, comme vous montre la documentation.\nConcentrons-nous donc sur la création du nouveau site à l’aide d’Hugo.\nNous n'avons qu'à utiliser la ligne de commande prévue à cet effet :\nhugo new site snipcart-hugo\nArchitecture\nCette commande va générer un squelette de base pour votre projet. Votre répertoire devrait ressembler à ça :\n│   config.toml\n│\n├───archetypes\n├───content\n├───data\n├───layouts\n├───static\n└───themes\nLes options de configuration se trouvent dans le fichier config.toml. 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.\nPas la peine de nous plonger dans les rouages internes d’Hugo ici.\nEn gros, dans ce tutorial, nous allons créer des fichiers dans le répertoire data qui a pour but de stocker des données additionnelles qui peuvent être utilisées lors de la génération du site.\nNous allons aussi ajouter quelques modèles dans le dossier layouts, l’endroit où les modèles Hugo sont stockés par défaut.\nLe dossier static peut être utilisé pour stocker des fichiers de type CSS, JavaScript ou bien encore des images. Dans notre démo, nous ajouterons un dossier images qui contiendra les images des produits.\nNaturellement, 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.\nLes thèmes\nNous 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.\nCet article 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.).\nVous pouvez aussi aller parcourir l’annuaire officiel de quelques-uns des meilleurs thèmes pour Hugo.\n2. Créer un fichier JSON statique pour les produits de notre boutique\nOK, passons donc à la configuration de nos produits : un dictionnaire Klingon et un pistolet laser. Nous aurions pu utiliser un CMS headless ou statique pour cette partie (comme nous l’avons déjà fait\nauparavant).\nVu le modeste objectif de cet article, nous allons simplement créer un fichier .json statique pour référencer nos produits.\nHugo propose une super fonction appelée getJSON 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 data, nous aurions pu nous contenter d’utiliser .Site.Data.Products à place, mais nous voulions vous monter qu'il était possible d’interagir avec des APIs externes.\nNous allons devoir ajouter un nouveau fichier products.json dans le dossier data.\n[\n  {\n    \"id\": \"1\",\n    \"name\": \"Dictionnaire Klingon\",\n    \"price\": 34.87,\n    \"image\": \"\/images\/dictionary.jpg\",\n    \"description\": \"nIvbogh tlhIngan dictionary qaStaHvIS veng SuvwI'\",\n    \"url\": \"http:\/\/snipcart-hugo.netlify.com\"\n  },\n  {\n    \"id\": \"2\",\n    \"name\": \"Phaser du Captain Kirk\",\n    \"description\": \"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.\",\n    \"price\": 145.98,\n    \"image\": \"\/images\/phaser.png\",\n    \"url\": \"http:\/\/snipcart-hugo.netlify.com\"\n  }\n]\n3. Génération des modèles pour Hugo\nLa 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 les dépendances pour Snipcart.\nNous 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\".\nRemarque : les produits Snipcart sont définis directement dans le code HTML à l’aide de simples attributs data.\nPlus de détails ici.\nDans le répertoire layouts nous allons ajouter un nouveau modèle index.html. Ce fichier sera celui utilisé par défaut et sera le premier à être généré par Hugo.\nlayouts\/index.html\n{{ partial \"header.html\" . }}\n\n{{ $products := getJSON \"\/data\/products.json\" }}\n\n&lt;section class=\"container\"&gt;\n    &lt;div class=\"row\"&gt;\n        {{ range $products }}\n            {{ partial \"product.html\" . }}\n        {{ end }}\n    &lt;\/div&gt;\n&lt;\/section&gt;\n\n{{ partial \"footer.html\" }}\nNous avons mentionné la méthode getJSON un peu plus haut, nous allons l’utiliser dans notre modèle de page index.html.\nNous 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 product.html qui va être chargé du rendu.\nComme vous pouvez voir, nous importons aussi les fichiers header.html, footer.html et product.html. Nous verrons ce qu'ils contiennent en détail.\nAvant d’aller plus loin, allons d’abord dans le répertoire layouts et créons un dossier partials. 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 {{ partial … }}. L'autre chose importante à savoir est pourquoi nous avons mis un point . après product.html. Cela signifie que nous incluons les données du produit courant dans le modèle product.html.\nlayouts\/partials\/header.html\nComme 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.\nAjoutez-le dans le dossier layouts\/partials.\n&lt;!DOCTYPE html&gt;\n&lt;html xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\" xml:lang=\"en\" lang=\"en-us\"&gt;\n  &lt;head&gt;\n    &lt;meta http-equiv=\"content-type\" content=\"text\/html; charset=utf-8\" \/&gt;\n    &lt;meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1.0, maximum-scale=1\"\n    \/&gt;\n\n    &lt;title&gt;Intégration de Snipcart dans Hugo!&lt;\/title&gt;\n\n    &lt;link\n      id=\"snipcart-theme\"\n      type=\"text\/css\"\n      href=\"https:\/\/cdn.snipcart.com\/themes\/2.0\/base\/snipcart.min.css\"\n      rel=\"stylesheet\"\n    \/&gt;\n    &lt;link\n      rel=\"stylesheet\"\n      href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/materialize\/0.98.0\/css\/materialize.min.css\"\n    \/&gt;\n    &lt;link\n      href=\"https:\/\/fonts.googleapis.com\/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    \/&gt;\n  &lt;\/head&gt;\n\n  &lt;body&gt;\n    &lt;div class=\"container\"&gt;\n      &lt;nav&gt;\n        &lt;div class=\"nav-wrapper\"&gt;\n          &lt;a href=\"#\" class=\"brand-logo\"&gt;Star Trek shop&lt;\/a&gt;\n          &lt;ul id=\"nav-mobile\" class=\"right hide-on-med-and-down\"&gt;\n            &lt;li class=\"snipcart-summary\"&gt;\n              &lt;a href=\"#\" class=\"snipcart-checkout\"&gt;\n                View cart (&lt;span class=\"snipcart-total-items\"&gt;0&lt;\/span&gt;)\n              &lt;\/a&gt;\n            &lt;\/li&gt;\n          &lt;\/ul&gt;\n        &lt;\/div&gt;\n      &lt;\/nav&gt;\n    &lt;\/div&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;\nNous avons choisi d’utiliser le framework MaterializeCSS 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.\nVous pouvez également voir que les fichiers requis par Snipcart sont appelés dans ce fichier et que nous avons ajouté un raccourci vers le panier d’achat pour que les clients puissent accéder à leur commande en cours.\nParfait ! Prochaine étape : le modèle partiel de pied de page pour terminer la structure de base de notre fichier HTML.\nlayouts\/partials\/footer.html\n        &lt;div class=\"container\"&gt;\n            &lt;footer class=\"page-footer\"&gt;\n                &lt;div class=\"footer-copyright\"&gt;\n                    &lt;div class=\"container\"&gt;\n                        Snipcart integration with Hugo\n                    &lt;\/div&gt;\n                &lt;\/div&gt;\n            &lt;\/footer&gt;\n        &lt;\/div&gt;\n\n        &lt;script src=\"\/\/ajax.googleapis.com\/ajax\/libs\/jquery\/1.9.1\/jquery.min.js\" type=\"text\/javascript\"&gt;&lt;\/script&gt;\n\n        &lt;script type=\"text\/javascript\" id=\"snipcart\" src=\"https:\/\/cdn.snipcart.com\/scripts\/2.0\/snipcart.js\" data-api-key=\"M2E5YjA3NjMtYzRiYS00YzVjLWEyYWYtNDY5ZDI0OWZhYjg5\"&gt;&lt;\/script&gt;\n\n        &lt;script&gt;\n            Snipcart.execute('registerLocale', 'en', {\n                powered_by:\n                \"HoS 'ej pong ngaQ \"\n            });\n        &lt;\/script&gt;\n    &lt;\/body&gt;\n&lt;\/html&gt;\nEnfin, nous allons devoir générer le modèle qui va afficher le détail d’un produit. Appelons le product.html.\nlayouts\/partials\/product.html\n&lt;div class=\"col s6\"&gt;\n  &lt;h2 class=\"header\"&gt;{{ .name }}&lt;\/h2&gt;\n  &lt;div class=\"card horizontal\"&gt;\n    &lt;div class=\"card-image\"&gt;\n      &lt;img src=\"{{ .image }}\" \/&gt;\n    &lt;\/div&gt;\n    &lt;div class=\"card-stacked\"&gt;\n      &lt;div class=\"card-content\"&gt;\n        &lt;p&gt;{{ .description }}&lt;\/p&gt;\n      &lt;\/div&gt;\n      &lt;div class=\"card-action\"&gt;\n        &lt;button\n          class=\"snipcart-add-item waves-effect waves-light btn\"\n          data-item-id=\"{{ .id }}\"\n          data-item-name=\"{{ .name }}\"\n          data-item-price=\"{{ .price }}\"\n          data-item-url=\"{{ .url }}\"\n        &gt;\n          &lt;i class=\"material-icons right\"&gt;shopping_cart&lt;\/i&gt;\n          Add to cart\n        &lt;\/button&gt;\n      &lt;\/div&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n&lt;\/div&gt;\nPuisque nous passons le produit en cours dans notre modèle index.html, nous pouvons maintenant accéder à tous les champs des données de notre fichier JSON. 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.\nIl est temps de lancer Hugo et de regarder à quoi ressemble ce site fantaisiste !\nhugo server\n(Je garde ma capture d’écran de notre boutique Star Trek pour la fin, tenez-vous prêts)\n4. Configuration du déploiement d’Hugo sur Netlify\nDernier point et non des moindres : héberger tout ça !\nNous avons choisi de déployer notre démo avec Hugo à l’aide de l’extraordinaire service de nos amis de chez Netlify.\nAvant de toucher à quoi que ce soit dans Netlify, je vous suggère de créer un fichier .gitkeep dans votre dossier content. 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.\nUne fois le fichier .gitkeep 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.\nNetlify va récupérer automatiquement le code depuis GitHub et déployer votre site web. Et voilà !\nDémo de site Hugo et dépôt GitHub\nBon, il est temps de vous monter notre chef-d’œuvre.\nAlors c'est classe non ? Maintenant allez voir le site et le code source par vous-même :\n\nVoir Demo Snipcart + Hugo\nVoir Dépot GitHub\n\nConclusion et ressources supplémentaires\nBon, je crois que notre travail est terminé mes amis.\nAu cas où vous vous demanderiez si le résultat final est assez rapide, vous pouvez utiliser un autre outil assez cool de Netlify : Testmysite.io. Notre démo obtient un score de 87\/100, c'est pas si mal.\nAu fait, si vous développez un site Jamstack pour un client, vous voudrez peut-être effectuer un suivi de sa performance à l’aide de Speedtracker, un outil open source. Les équipes\ntechniques seront peut-être intéressées par ce workflow de publication pour Hugo.\nPour les clients\nVous 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 :\n\nForestry.io\nDatoCMS\nNetlify CMS\nAppernatic\n\nPour une revue plus détaillée des outils à destination des clients, des limites et des bénéfices, reportez-vous à ce guide complet.\nHugo 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.\nC’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. 😀\nIl est maintenant temps d’arrêter de lire ce blog et d’aller fabriquer quelque chose de génial.",
      "content_html": "<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>\nDans 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>\n<blockquote>\n<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>\n</blockquote>\n<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>\n<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>\n<p>Dans ce tutoriel nous verrons :</p>\n<ol>\n<li>Comment générer votre site statique avec le générateur de site Hugo,</li>\n<li>Comment y intégrer ensuite facilement le panier d’achat de la plate-forme\nSnipcart,</li>\n<li>Comment déployer votre site e-commerce sur Netlify.</li>\n</ol>\n<p>Mais d’abord un petit mot sur l’outil central que nous allons utiliser pour faire ceci.</p>\n<h2 id=\"hugo-un-generateur-de-site-statique-super-rapide\">Hugo : un générateur de site statique super rapide</h2>\n<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>\n<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\nrecommandons <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>\n<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>\n<blockquote>\n<p><em>Psst</em> : Si vous vous demandez encore ce que sont les générateurs de site\nstatique et pourquoi il faut vous y intéresser, jetez-vous sur\n<a href=\"https://davidwalsh.name/introduction-static-site-generators\" target=\"_blank\" rel=\"noopener noreferrer\">l’intro d’Eduardo Bouças</a>.</p>\n</blockquote>\n<h2 id=\"tutoriel-hugo-site-produits-modeles-et-deploiement\">Tutoriel Hugo : site, produits, modèles et déploiement</h2>\n<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>\n<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>\n<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>\nConcentrons-nous donc sur la création du nouveau site à l’aide d’Hugo.</p>\n<p>Nous n'avons qu'à utiliser la ligne de commande prévue à cet effet :</p>\n<pre><code class=\"language-sh hljs bash\">hugo new site snipcart-hugo</code></pre>\n<h4 id=\"architecture\">Architecture</h4>\n<p>Cette commande va générer un squelette de base pour votre projet. Votre répertoire devrait ressembler à ça :</p>\n<pre><code class=\"language-sh hljs bash\">│   config.toml\n│\n├───archetypes\n├───content\n├───data\n├───layouts\n├───static\n└───themes</code></pre>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h4 id=\"les-themes\">Les thèmes</h4>\n<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>\n<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>\n<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>\n<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>\n<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>\n<a href=\"https://www.contentful.com/blog/2016/02/10/snipcart-middleman-contentful/\" target=\"_blank\" rel=\"noopener noreferrer\">auparavant</a>).</p>\n<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>\n<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>\n<p>Nous allons devoir ajouter un nouveau fichier <code>products.json</code> dans le dossier <code>data</code>.</p>\n<pre><code class=\"language-json hljs json\">[\n  {\n    <span class=\"hljs-attr\">\"id\"</span>: <span class=\"hljs-string\">\"1\"</span>,\n    <span class=\"hljs-attr\">\"name\"</span>: <span class=\"hljs-string\">\"Dictionnaire Klingon\"</span>,\n    <span class=\"hljs-attr\">\"price\"</span>: <span class=\"hljs-number\">34.87</span>,\n    <span class=\"hljs-attr\">\"image\"</span>: <span class=\"hljs-string\">\"/images/dictionary.jpg\"</span>,\n    <span class=\"hljs-attr\">\"description\"</span>: <span class=\"hljs-string\">\"nIvbogh tlhIngan dictionary qaStaHvIS veng SuvwI'\"</span>,\n    <span class=\"hljs-attr\">\"url\"</span>: <span class=\"hljs-string\">\"http://snipcart-hugo.netlify.com\"</span>\n  },\n  {\n    <span class=\"hljs-attr\">\"id\"</span>: <span class=\"hljs-string\">\"2\"</span>,\n    <span class=\"hljs-attr\">\"name\"</span>: <span class=\"hljs-string\">\"Phaser du Captain Kirk\"</span>,\n    <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>,\n    <span class=\"hljs-attr\">\"price\"</span>: <span class=\"hljs-number\">145.98</span>,\n    <span class=\"hljs-attr\">\"image\"</span>: <span class=\"hljs-string\">\"/images/phaser.png\"</span>,\n    <span class=\"hljs-attr\">\"url\"</span>: <span class=\"hljs-string\">\"http://snipcart-hugo.netlify.com\"</span>\n  }\n]</code></pre>\n<h3 id=\"3-generation-des-modeles-pour-hugo\">3. Génération des modèles pour Hugo</h3>\n<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>\n<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>\n<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>\n<a href=\"https://docs.snipcart.com/configuration/product-definition\" target=\"_blank\" rel=\"noopener noreferrer\">Plus de détails ici</a>.</p></aside>\n<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>\n<h4 id=\"layouts-index-html\">layouts/index.html</h4>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"header.html\"</span> . }}\n\n{{ $products := getJSON <span class=\"hljs-string\">\"/data/products.json\"</span> }}\n\n&lt;section class=<span class=\"hljs-string\">\"container\"</span>&gt;\n    &lt;div class=<span class=\"hljs-string\">\"row\"</span>&gt;\n        {{ <span class=\"hljs-keyword\">range</span> $products }}\n            {{ partial <span class=\"hljs-string\">\"product.html\"</span> . }}\n        {{ end }}\n    &lt;/div&gt;\n&lt;/section&gt;\n\n{{ partial <span class=\"hljs-string\">\"footer.html\"</span> }}</code></pre>\n<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>\n<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>\n<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>\n<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>\n<h4 id=\"layouts-partials-header-html\">layouts/partials/header.html</h4>\n<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>\nAjoutez-le dans le dossier <code>layouts/partials</code>.</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-meta\">&lt;!DOCTYPE <span class=\"hljs-meta-keyword\">html</span>&gt;</span>\n<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>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span>\n    <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>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">meta</span>\n      <span class=\"hljs-attr\">name</span>=<span class=\"hljs-string\">\"viewport\"</span>\n      <span class=\"hljs-attr\">content</span>=<span class=\"hljs-string\">\"width=device-width, initial-scale=1.0, maximum-scale=1\"</span>\n    /&gt;</span>\n\n    <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>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link</span>\n      <span class=\"hljs-attr\">id</span>=<span class=\"hljs-string\">\"snipcart-theme\"</span>\n      <span class=\"hljs-attr\">type</span>=<span class=\"hljs-string\">\"text/css\"</span>\n      <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css\"</span>\n      <span class=\"hljs-attr\">rel</span>=<span class=\"hljs-string\">\"stylesheet\"</span>\n    /&gt;</span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link</span>\n      <span class=\"hljs-attr\">rel</span>=<span class=\"hljs-string\">\"stylesheet\"</span>\n      <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>\n    /&gt;</span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link</span>\n      <span class=\"hljs-attr\">href</span>=<span class=\"hljs-string\">\"https://fonts.googleapis.com/icon?family=Material+Icons\"</span>\n      <span class=\"hljs-attr\">rel</span>=<span class=\"hljs-string\">\"stylesheet\"</span>\n    /&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span>\n    <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>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav</span>&gt;</span>\n        <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>\n          <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>\n          <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>\n            <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>\n              <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>\n                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>)\n              <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">a</span>&gt;</span>\n            <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">li</span>&gt;</span>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">ul</span>&gt;</span>\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">nav</span>&gt;</span>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></code></pre>\n<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>\n<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>\n<p>Parfait ! Prochaine étape : le modèle partiel de pied de page pour terminer la structure de base de notre fichier HTML.</p>\n<h4 id=\"layouts-partials-footer-html\">layouts/partials/footer.html</h4>\n<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>\n            <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>\n                <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>\n                    <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>\n                        Snipcart integration with Hugo\n                    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n                <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n            <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">footer</span>&gt;</span>\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n\n        <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>\n\n        <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>\n\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script</span>&gt;</span><span class=\"actionscript\">\n            Snipcart.execute(<span class=\"hljs-string\">'registerLocale'</span>, <span class=\"hljs-string\">'en'</span>, {\n                powered_by:\n                <span class=\"hljs-string\">\"HoS 'ej pong ngaQ \"</span>\n            });\n        </span><span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">script</span>&gt;</span>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></code></pre>\n<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>\n<h4 id=\"layouts-partials-product-html\">layouts/partials/product.html</h4>\n<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>\n  <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>\n  <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>\n    <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>\n      <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>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n    <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>\n      <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>\n        <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>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n      <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>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button</span>\n          <span class=\"hljs-attr\">class</span>=<span class=\"hljs-string\">\"snipcart-add-item waves-effect waves-light btn\"</span>\n          <span class=\"hljs-attr\">data-item-id</span>=<span class=\"hljs-string\">\"{{ .id }}\"</span>\n          <span class=\"hljs-attr\">data-item-name</span>=<span class=\"hljs-string\">\"{{ .name }}\"</span>\n          <span class=\"hljs-attr\">data-item-price</span>=<span class=\"hljs-string\">\"{{ .price }}\"</span>\n          <span class=\"hljs-attr\">data-item-url</span>=<span class=\"hljs-string\">\"{{ .url }}\"</span>\n        &gt;</span>\n          <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>\n          Add to cart\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">button</span>&gt;</span>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span></code></pre>\n<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>\n<p>Il est temps de lancer Hugo et de regarder à quoi ressemble ce site fantaisiste !</p>\n<pre><code class=\"language-sh hljs bash\">hugo server</code></pre>\n<p>(Je garde ma capture d’écran de notre boutique Star Trek pour la fin, tenez-vous prêts)</p>\n<h3 id=\"4-configuration-du-deploiement-d-hugo-sur-netlify\">4. Configuration du déploiement d’Hugo sur Netlify</h3>\n<p>Dernier point et non des moindres : héberger tout ça !</p>\n<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>\n<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>\n<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>\n<p>Netlify va récupérer automatiquement le code depuis GitHub et déployer votre site web. Et voilà !</p>\n<h2 id=\"demo-de-site-hugo-et-depot-github\">Démo de site Hugo et dépôt GitHub</h2>\n<p>Bon, il est temps de vous monter notre chef-d’œuvre.</p>\n<p>Alors c'est classe non ? Maintenant allez voir le site et le code source par vous-même :</p>\n<ul>\n<li>Voir <a href=\"http://snipcart-hugo.netlify.com\" target=\"_blank\" rel=\"noopener noreferrer\">Demo Snipcart + Hugo</a></li>\n<li>Voir <a href=\"https://github.com/snipcart/snipcart-hugo-integration\" target=\"_blank\" rel=\"noopener noreferrer\">Dépot GitHub</a></li>\n</ul>\n<h2 id=\"conclusion-et-ressources-supplementaires\">Conclusion et ressources supplémentaires</h2>\n<p>Bon, je crois que notre travail est terminé mes amis.</p>\n<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>\n<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\ntechniques 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>\n<h3 id=\"pour-les-clients\">Pour les clients</h3>\n<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>\n<ul>\n<li><a href=\"https://forestry.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Forestry.io</a></li>\n<li><a href=\"https://www.datocms.com/\" target=\"_blank\" rel=\"noopener noreferrer\">DatoCMS</a></li>\n<li><a href=\"https://www.netlifycms.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Netlify CMS</a></li>\n<li><a href=\"https://appernetic.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Appernatic</a></li>\n</ul>\n<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>\n<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>\n<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>\n<p>Il est maintenant temps d’arrêter de lire ce blog et d’aller fabriquer quelque chose de génial.</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/07/11/interview-kyle-matthews-gatsby/",
      "url": "https://jamstatic.fr/2017/07/11/interview-kyle-matthews-gatsby/",
      "title": "Questions à Kyle Mathews, créateur de Gatsby un générateur pour les sites basés sur React",
      "summary": "Entretien avec Kyle Mathews, le créateur de Gatsby, un générateur de site statique basé sur React.",
      "date_published": "2017-07-11T08:00:00+00:00","content_text": "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.\nAprès avoir travaillé pour différentes startups, Kyle Mathews a démissionné pour se consacrer à l’un de ses projets personnels à temps plein. Ce projet, Gatsby, est né de sa volonté de créer un site web qui lui évite d’avoir à utiliser autre chose que ReactJS.\nEntre 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.\nPour illustrer à quel point Gatsby est rapide, Mathews a écrit un clone d’Instagram conçu pour démontrer l’utilisation du pattern PRPL de Google afin d’obtenir le plus tôt possible un affichage des pixels à l’écran.\nCette 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\", explique Mathews.\n\nGatsby 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\n\nDans une interview à InfoQ, Mathews parle des motivations pour lesquelles il développe Gastby et de son avenir.\nQuels problèmes Gatsby essaie-t-il de résoudre ?\nGatsby essaie de résoudre la problématique de ce à quoi un\nframework de site web devrait ressembler en 2017. La plupart des positions\nadoptées par les frameworks web datent des premières générations du Web. Bien\nque ce soit d’excellents frameworks matures, ils ne sont pas conçus pour la\nmajorité du web d’aujourd’hui, dominé par des milliards de personnes qui\naccèdent au web avec des smartphones bon marché sur des réseaux peu fiables.\nPour qu'un site web soit rapide sur un smartphone, il doit rester assez\nindépendant du serveur, être capable de pré-extraire du code et des données et\nd’effectuer le rendu du contenu côté client.\nLes smartphones et les navigateurs sont largement assez rapides pour proposer de\nbonnes expériences de navigation sur le web — nous sommes juste ralentis par de\nvieux frameworks qui assument des connexions filaires, rapides et obligent les\npetits super-ordinateurs qui se trouvent dans notre proche d’attendre après des\nréseaux cellulaires peu fiables.\nGatsby intègre assez d’intelligence pour s'assurer que les sites se chargent\nrapidement et que naviguer sur un site soit visiblement rapide quel que soit\nl’état du réseau.\nLe design adaptatif a été une première étape importante pour le web mobile mais\nnous devons absolument aller vers un modèle d’architecture où les sites sont\nrendus côté client et téléchargent le contenu de manière intelligente.\nComment se positionne Gatsby par rapport aux autres générateurs de sites statiques, qu'ils soient basés sur React ou non ?\nIl hérite de tous les bénéfices des générateurs de site\nstatique traditionnels, à savoir une super performance, une sécurité accrue, un\ncoût de montée en charge moindre ainsi qu'une meilleure expérience de\ndéveloppement (une migration de bases de données ça vous rappelle quelque chose\n?). La v1 de Gatsby marque une nouvelle étape pour les générateurs de site\nstatique, en permettant l’intégration de CMS comme Contentful, WordPress et\nDrupal, et en embarquant tout un tas de fonctionnalités, activées par défaut,\nqui rendent votre site rapide dès le début — découpage du code en fonction du\nchemin demandé, Service Workers, le support du hors-ligne et bien plus.\nTous les autres générateurs de site statique ne font pas grand-chose pour aider\nles développeurs à travailler de manière moderne avec CSS et JS. Ils se\ncontentent de compiler (généralement du Markdown) en HTML et vous laisse le soin\nde configurer votre processus de génération d’assets vous-même. Gatsby prend\ntout ça en charge par défaut pour vous permettre de développer des sites web\nsophistiqués à l’aide des derniers outils et des dernières techniques.\nQuelle longévité peut-on espérer pour Gatsby ?\nGatsby connait un bel essor et est déjà le 4e générateur de\nsite statique après seulement deux ans. Il y a déjà quelques sites très visibles\nqui ont été lancés ou qui sont en cours de développement avec Gatsby. Nous avons\nrécemment dépassé les 200 contributeurs au niveau du cœur et les 500 000\ntéléchargements pour Gatsby. Pour que Gastby continue dans la durée, il va\nfalloir trouver un modèle économique rentable pour soutenir le développement du\ncœur de Gatsby et une solide communauté open-source qui développe et maintienne\ndes modèles et des intégrations de sources de données.\nQue prévoyez-vous ensuite ?\nMon objectif personnel est de travailler sur comment financer\nle développement du cœur de Gatsby. La priorité principale du projet est de\nproposer de plus en plus d’intégration avec des APIs, des CMS et des bases de\ndonnées pour rendre triviale la migration de sites existants ou de les refaire\navec un framework web moderne. Gatsby est déjà capable de faire tourner en\nproduction des sites sophistiqués et très rapides. La prochaine case à cocher de\nla liste sera de s'assurer de rendre aussi trivial l’import de données dans\nGatsby — où qu'elles se trouvent actuellement.\nBien que Gatsby ait bientôt deux ans, le projet en est encore à ses débuts et\nbeaucoup de parties utiles vont bientôt faire leur arrivée. Le système de\nplugins devrait permettre à la communauté de combler les manques et il en existe\ndéjà plus d’une trentaine qui permettent d’utiliser des technologies comme Sass,\nTypescript ou Preact.\nPour en savoir plus, rendez-vous sur le dépôt GitHub ou le site de Gatsby.",
      "content_html": "<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>\n<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>\n<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>\n<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>\n<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>\n<blockquote>\n<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>\n</blockquote>\n<p>Dans une interview à InfoQ, Mathews parle des motivations pour lesquelles il développe Gastby et de son avenir.</p>\n<h3 id=\"quels-problemes-gatsby-essaie-t-il-de-resoudre\">Quels problèmes Gatsby essaie-t-il de résoudre ?</h3>\n<p>Gatsby essaie de résoudre la problématique de ce à quoi un\nframework de site web devrait ressembler en 2017. La plupart des positions\nadoptées par les frameworks web datent des premières générations du Web. Bien\nque ce soit d’excellents frameworks matures, ils ne sont pas conçus pour la\nmajorité du web d’aujourd’hui, dominé par des milliards de personnes qui\naccèdent au web avec des smartphones bon marché sur des réseaux peu fiables.</p>\n<p>Pour qu'un site web soit rapide sur un smartphone, il doit rester assez\nindépendant du serveur, être capable de pré-extraire du code et des données et\nd’effectuer le rendu du contenu côté client.</p>\n<p>Les smartphones et les navigateurs sont largement assez rapides pour proposer de\nbonnes expériences de navigation sur le web — nous sommes juste ralentis par de\nvieux frameworks qui assument des connexions filaires, rapides et obligent les\npetits super-ordinateurs qui se trouvent dans notre proche d’attendre après des\nréseaux cellulaires peu fiables.</p>\n<p>Gatsby intègre assez d’intelligence pour s'assurer que les sites se chargent\nrapidement et que naviguer sur un site soit visiblement rapide quel que soit\nl’état du réseau.</p>\n<p>Le design adaptatif a été une première étape importante pour le web mobile mais\nnous devons absolument aller vers un modèle d’architecture où les sites sont\nrendus côté client et téléchargent le contenu de manière intelligente.</p>\n<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>\n<p>Il hérite de tous les bénéfices des générateurs de site\nstatique traditionnels, à savoir une super performance, une sécurité accrue, un\ncoût de montée en charge moindre ainsi qu'une meilleure expérience de\ndéveloppement (une migration de bases de données ça vous rappelle quelque chose\n?). La v1 de Gatsby marque une nouvelle étape pour les générateurs de site\nstatique, en permettant l’intégration de CMS comme Contentful, WordPress et\nDrupal, et en embarquant tout un tas de fonctionnalités, activées par défaut,\nqui rendent votre site rapide dès le début — découpage du code en fonction du\nchemin demandé, Service Workers, le support du hors-ligne et bien plus.</p>\n<p>Tous les autres générateurs de site statique ne font pas grand-chose pour aider\nles développeurs à travailler de manière moderne avec CSS et JS. Ils se\ncontentent de compiler (généralement du Markdown) en HTML et vous laisse le soin\nde configurer votre processus de génération d’assets vous-même. Gatsby prend\ntout ça en charge par défaut pour vous permettre de développer des sites web\nsophistiqués à l’aide des derniers outils et des dernières techniques.</p>\n<h3 id=\"quelle-longevite-peut-on-esperer-pour-gatsby\">Quelle longévité peut-on espérer pour Gatsby ?</h3>\n<p>Gatsby connait un bel essor et est déjà le 4e générateur de\nsite statique après seulement deux ans. Il y a déjà quelques sites très visibles\nqui ont été lancés ou qui sont en cours de développement avec Gatsby. Nous avons\nrécemment dépassé les 200 contributeurs au niveau du cœur et les 500 000\ntéléchargements pour Gatsby. Pour que Gastby continue dans la durée, il va\nfalloir trouver un modèle économique rentable pour soutenir le développement du\ncœur de Gatsby et une solide communauté open-source qui développe et maintienne\ndes modèles et des intégrations de sources de données.</p>\n<h3 id=\"que-prevoyez-vous-ensuite\">Que prévoyez-vous ensuite ?</h3>\n<p>Mon objectif personnel est de travailler sur comment financer\nle développement du cœur de Gatsby. La priorité principale du projet est de\nproposer de plus en plus d’intégration avec des APIs, des CMS et des bases de\ndonnées pour rendre triviale la migration de sites existants ou de les refaire\navec un framework web moderne. Gatsby est déjà capable de faire tourner en\nproduction des sites sophistiqués et très rapides. La prochaine case à cocher de\nla liste sera de s'assurer de rendre aussi trivial l’import de données dans\nGatsby — où qu'elles se trouvent actuellement.</p>\n<p>Bien que Gatsby ait bientôt deux ans, le projet en est encore à ses débuts et\nbeaucoup de parties utiles vont bientôt faire leur arrivée. Le système de\nplugins devrait permettre à la communauté de combler les manques et il en existe\ndéjà plus d’une trentaine qui permettent d’utiliser des technologies comme Sass,\nTypescript ou Preact.</p>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/07/05/vincent-voyer-algolia/",
      "url": "https://jamstatic.fr/2017/07/05/vincent-voyer-algolia/",
      "title": "Questions à Vincent Voyer, ingénieur fullstack chez Algolia",
      "summary": "Présentation d’Algolia, un service de recherche personnalisé pour votre site web.",
      "date_published": "2017-07-05T10:34:00+00:00","content_text": "Sur un site généré, l’interactivité est reléguée côté client, au navigateur,\ndonc à JavaScript. Si vous souhaitez intégrer une partie dynamique sur votre\nsite statique, il faudra donc faire appel à une API externe. C’est tout le\nconcept de la JAMStack\ndont nous parlons sur ce blog. Et c'est typiquement le cas si vous souhaitez\nintégrer un moteur de recherche sur votre site généré. Heureusement pour nous,\nen ce qui concerne la recherche, Algolia propose une\nAPI et des bibliothèques qui vont faciliter l’intégration d’une recherche de\nqualité.\nAvec une formule community gratuite qui\nautorise 10 000 enregistrements et 100 000 opérations par mois, c'est\nune solution intéressante à tester pour des sites statiques à trafic modeste. En\nproposant une personnalisation et une expérience utilisateur poussée, on peut\ndire qu'Algolia est ce qui se fait de mieux en la matière.\nNous sommes donc allés interroger Vincent Voyer, ingénieur JavaScript fullStack\nchez Algolia pour qu'il nous en dise un peu plus sur ce service en pleine\ncroissance qui a réussi en l’espace de quelque temps à conquérir la communauté\ndes développeurs web.\nFrank : Bonjour Vincent, on va peut-être commencer par te présenter ?\nVincent Voyer : Donc moi je suis Vincent Voyer, sur Twitter j'étais zeroload\nmaintenant je suis vvoyer, je suis développeur\nJavaScript. Pendant un moment j'ai fait expert performance web, j'ai travaillé\nchez Fasterize dont j'étais cofondateur, j'ai travaillé chez Mappy, j'ai fait du\nconseil en développement freelance pour lemonde.fr, France Télévision…\nAujourd’hui je suis chez Algolia où je suis toujours développeur JavaScript. Je\ntravaille principalement sur des bibliothèques open source qui permettent\nd’intégrer Algolia sur tous les sites web.\nJ'aime le basket, j'essaie d’inviter des co-workers toutes les semaines, c'est\ncool, ce soir on y va, on est trois plus le reste de l’équipe. J'ai 32 ans, une\nfemme et un enfant. Voilà.\nFrank : T'as l’air de bien t'éclater, qu'est-ce qui te plaît chez Algolia ?\nVincent Voyer : J'ai une petite anecdote à ce sujet. Quand j'étais\ndéveloppeur freelance, je suis allé à DotJS, il y avait un stand Algolia, je\nsuis simplement passé devant. Après il y avait une conférence d’un gars qui\ns'appelle Substack, James Halliday, un gros\ndéveloppeur JavaScript de la communauté npm, qui disait que la recherche sur le\nsite de npm n'était pas super, quand on met deux mots ça fait un ET et pas un\nOU, il n'y a pas de tolérance aux fautes de frappe, c'est pas super.\nJe repasse devant le stand Algolia et là il y avait une énergie, il y avait\nAlexandre Colin, qui était Solution Engineer à cette époque-là, qui présentait\nAlgolia. Ils me disent qu'ils recrutent. Je me dis je pourrais faire un petit\ntest avec Algolia pour faire un moteur de recherche de paquets npm.\nDu coup j'ai fait ça, en fin d’année 2014, un petit test de recherche de paquets\nnpm : j'ai pris 300 000 paquets npm, j’ai créé un index de recherche\nAlgolia avec. J'ai commencé à faire les premières requêtes, ça prenait 4\nmillisecondes.\nJ'étais capable de faire une recherche dans un champ de recherche, d’exécuter\nune requête, élaborée à l’aide du framework Express et d’obtenir des résultats\nen temps réel en 4ms en incluant le temps de latence réseau, etc. 4ms, 10ms, je\nme suis dit : wouah c'est quand même incroyable !\nVoilà, ma première rencontre avec Algolia c'était ça. Et très vite après j'ai\nété en contact avec eux, car je voulais refaire leur client JavaScript, qui ne\nme plaisait pas en tant que développeur JavaScript…\nFrank : Comment définirais-tu l’activité d’Algolia ?\nVincent Voyer : Algolia c'est un moteur de recherche en Saas (Software as a Service), en gros c'est une\nAPI qui permet de créer un index de recherche pour son propre site web. Par\nexemple si vous avez un site web e-commerce avec 100 000 produits. Pour\nfaire votre moteur de recherche : soit vous le faites vous-même, ou vous prenez\nElasticSearch, etc. Du coup, vous devez gérer l’infrastructure, etc. Soit vous\noptez pour une solution Saas comme Algolia, qui vous permet de mettre tous vos\nproduits dans un index de recherche et qui vous expose une API pour ajouter ou\nmodifier des produits mais aussi pour chercher vos produits.\nAujourd’hui les gros clients d’Algolia dont on peut parler c'est Stripe, Strava,\n8tracks, CrunchBase, Periscope, Twitch, etc. Donc c'est des gros noms, ça marche\nplutôt bien. Algolia existe depuis cinq ans maintenant.\nFrank : Pourquoi est-ce que le moteur de recherche d’Algolia est aussi rapide aujourd’hui ?\nVincent Voyer : C’est parce qu'au début c'était un SDK pour développeurs\nd’applications mobiles, pour les aider à mettre un champ de recherche dans leur\napplication mobile sur les données des utilisateurs. Et comme il fallait que ce\nSDK tourne sur tous les types de mobiles, que ça soit un vieux smartphone Nokia\nou le dernier iPhone, du coup tout le code était optimisé pour que ce soit\nvraiment rapide à tout moment, donc optimisé à la milliseconde près.\nCe produit-là n'a pas fonctionné et quand ils ont fait Ycombinator, un\nincubateur de startups aux États-Unis, ils leur ont dit : \"C’est pas ça qu'il\nfaut faire en fait, c'est une API Web\". Ils ont fait ça et ça a fonctionné et\naujourd’hui c'est notre principal produit, l’API et le moteur de recherche\nd’Algolia.\nFrank : 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 ?\nVincent Voyer : Oui c'est ça. Généralement votre site de e-commerce, il a\nune base de données avec des produits structurés. Beaucoup de gens passent par\nGoogle pour rechercher des produits. Ils vont chercher par exemple \"FNAC +\niPhone\" sur Google. Le truc c'est qu'ils vont arriver sur le site de la FNAC et\nsi ça se trouve, ils vont vouloir faire une nouvelle recherche. Donc là sur le\nsite de la FNAC il y a un champ recherche et ce champ de recherche là il faut\nqu'il soit beau. Ce que propose Google c'est une recherche dans l’Internet au\nglobal, mais ils ne proposent pas d’implémenter un bon moteur de recherche pour\nson site web. En fait ils le proposaient, ça s'appelait\nGoogle Site Search,\nils proposaient aux éditeurs de site web de mettre un champ de recherche avec\ndes pages de résultats dédiées au site en cours, c'est pas les résultats dans le\nmonde. Mais c'est pas suffisant.\nNous ce qu'on a voulu proposer c'est qu'on sait qu'un site de e-commerce il a\nune base de données avec des objets structurés et on s'est rendu compte que pour\nfournir une bonne expérience de recherche, avec de la tolérance à la faute de\nfrappe, avec du filtrage par type d’objet, il fallait pouvoir connaître la\nstructure d’un objet. Il faut savoir qu'une page produit, elle a une catégorie,\npour pouvoir proposer aux gens de filtrer sur cette catégorie-là. Du coup Google\naujourd’hui il ne sait pas faire ça, il essaie de déterminer en fonction de ses\nalgorithmes ce dont parle une page, mais dans les résultats Google on va pas\npouvoir dire : \"je veux tous les iPhone\", on va pouvoir l’exprimer avec une\nrequête, mais s'il le faut, cette requête elle va nous renvoyer des iPhone, des\ncoques d’iPhone, des livres sur l’iPhone, etc. Nous ce qu'on propose c'est des\nobjets structurés qui permettent ensuite aux développeurs de site web de créer\ndes menus, des sliders pour filtrer, etc.\nFrank : 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 Algolia pour Magento, j'étais content de voir arriver un nouvel acteur qui décide de proposer une expérience de recherche au top\nVincent Voyer : Les gens s'attendent aujourd’hui à avoir la meilleure\nexpérience de recherche, qui est celle de Google. Et Google ils ont fait un truc\nqui s'appelle Instant Search, qui affiche des propositions au fur et à mesure\nqu'on tape notre requête. Donc déjà tous les moteurs de recherche ont dû se\nbaser sur ça et proposer le Search as you Type. C’est super important car ça\npermet à la personne de ne pas avoir à taper \"iPhone\" puis de taper sur la\ntouche Entrée, se rendre compte qu'elle veut changer, revenir en arrière, etc.\nLà c'est au fur et à mesure, c'est naturel. Ça, ça vient aussi du fait que ça a\nété développé sur mobile à la base, car c'est encore plus compliqué de faire des\nworkflows de navigation sur mobile, du coup sur mobile il faut que ce soit le\nplus simple possible. L'autre paramètre qui a été pris en compte c'est la\ntolérance aux fautes de frappe.\nToutes ces choses-là, il y a des moteurs de recherche qui les font, mais chez\nAlgolia on s'est rendu compte qu'il n'y en avait aucun qui remplissait tous les\ncritères : une bonne performance, la gestion des fautes de frappe, du faceting\navancé et des résultats personnalisés, à savoir la capacité à dire : j'ai une\nliste d’iPhone, mais je veux en faire remonter certains dans les résultats. Par\nexemple sur la requête \"iPhone\" je vais vouloir favoriser certains résultats.\nPar exemple je ne veux pas que ce soit les coques d’iPhone qui apparaissent en\npremier, je veux que ce soit les iPhone. Nous on a par exemple un custom\nranking qui fait que ce sont les produits les plus consultés qui vont remonter\nde façon naturelle. Au fur et à mesure que les gens vont sur la page iPhone, on\nva mettre l’index de recherche à jour et l’affichage des résultats va être\nnaturel par rapport à ce que les gens demandent.\nAlgolia propose donc toutes ces fonctionnalités, performance, typo tolérance,\nfaceting et recherche avancés, que ne proposent pas forcément les autres moteurs\nde recherche.\nFrank : OK, revenons un peu au statique. Vous êtes présents sur beaucoup de sites de documentation ou de recherche de paquets comme yarn. Ces sites sont générés à l’aide de différents outils. Comment avez-vous implémenté tout ça ?\nVincent Voyer : La stratégie d’Algolia c'est d’avoir une super bonne\nDeveloper Experience et d’essayer de capter un maximum de développeurs sur\nnotre plate-forme forcément, pour que tout le monde soit au courant qu'on est un\nsuper moteur de recherche. Sachant que nous-mêmes nous sommes développeurs, et\nque nos outils c'est npm, c'est yarn, ça va être Hacker News, les différents\nframeworks PHP comme Laravel ou Symfony, React pour le monde JavaScript. Tout ça\nce sont nos outils de tous les jours.\nEt on a fait un jour le constat que sur tous ces sites-là, c'est souvent\ndifficile de trouver l’information qu'on cherche. Donc on a fait un projet qui\ns'appelle DocSearch, qui est en gros\nun crawler de documentation pour les développeurs. DocSearch fournit aujourd’hui\nde la recherche pour les développeurs sur plus de 300 sites web. Donc quand vous\ncherchez sur le site web de React, de Flow, de Symfony, de Laravel, en fait\nc'est Algolia qui est derrière. On l’affiche, c'est pour ça qu'on rend ce\nservice gratuitement, c'est parce que derrière ça va faire parler de nous et ça\nnous permet à nous en interne d’être plus efficaces.\nEn faisant cela, nous avons pollinisé d’autres communautés et plein d’autres\nsites ce sont dit : \"Hé, je veux la même recherche, c'est super !\"\nDonc on a construit au fur et à mesure ces différents projets.\nFrank: Comment ça fonctionne ?\nVincent Voyer : DocSearch c'est un crawler de site web avec des\nconfigurations par site. Pour chaque site on va dire : le nom d’une méthode dans\nl’API, c'est dans cette balise HTML, etc. Ça ressemble un petit peu à ce que\nfait Google, mais là c'est fait de façon contrôlée, c'est pas un algorithme\ngénérique, on cible exactement ce à quoi on veut que ressemble la documentation.\nDonc on a fait ça, c'était un des premiers gros trucs pour la communauté des\ndéveloppeurs, et à côté de ça on a aussi tout ce qui est outillage qu'on fournit\naux développeurs qui utilisent Algolia.\nCe dont on a envie c'est qu'à aucun moment ils se disent \"Algolia ça a l’air\nd’être un super moteur de recherche, mais c'est juste une API, y’a rien\nderrière, du coup je suis obligé de tout construire. Si j'utilise Symfony, il va\nfalloir que je développe mon propre connecteur Symfony-Algolia, si j'utilise\nJekyll pareil, si j'utilise React il va falloir que je fasse mes propres\ncomposants qui vont faire la liaison entre des composants React et la recherche\nsur Algolia.\"\nDu coup c'est ce qu'on a fait au fur et à mesure, pour tous les langages de\nprogrammation les plus utilisés. On a 14 clients d’API je crois (JavaScript,\nRuby, Python, Go, etc.).\nCe qui permet à chaque développeur de ne pas avoir à implémenter son propre\nclient d’API qui se base sur notre API. On les a vraiment créés pour eux. La\nplupart des boîtes s'arrêtent à trois et ils se disent pour les autres \"c'est la\ncommunauté qui les fera à un certain moment\". Ça marche ou ça ne marche pas, la\nqualité est là ou pas. Nous on les a faits pour tout le monde, tout est open\nsource donc les gens participent.\nEt après on est allé encore plus loin, car les clients d’API c'est assez bas\nniveau, sachant que sur un site web ce que je veux c'est une page de recherche\navec un champ recherche, avec une navigation, un slider sur le prix, etc.\nDonc on a créé des bibliothèques plus haut niveau qui permettent d’exprimer ça\npour le développeur. Il est capable à l’aide de widgets de dire \"là je mets un\nchamp de recherche, là je mets un sélecteur sur les genres de films, là je mets\nun slider sur l’année de sortie du film\", ce genre de choses.\nTout ça il peut l’exprimer à l’aide\nd’InstantSearch, qui est\nun projet qui englobe des sous-projets. InstantSearch c'est un ensemble de\nbibliothèques haut-niveau pour implémenter de la recherche avec Algolia.\nFrank: C’est complémentaire avec DocSearch dont tu parlais tout à l’heure ?\nVincent Voyer : Non, ce sont deux produits différents, on a plein de\nproduits qu'on essaie de regrouper au fur et à mesure. InstantSearch c'est\nvraiment pour les clients d’Algolia généralement qui vont vouloir implémenter de\nla recherche sur leur application React ou VueJS par exemple, de façon plus\nsimple que de devoir faire des appels à l’API d’Algolia qui est très puissante,\ncertes, mais elle est puissante parce qu'elle est modulaire, donc nous on cache\nun peu cette modularité pour répondre à la plupart des cas d’utilisation qu'ont\nles gens, tout en laissant la porte ouverte pour des trucs un petit peu plus\navancés qui seront faits avec des paramètres différents.\nInstantSearch c'est disponible pour du Vanilla JavaScript, C’est-à-dire sans\nframework, pour React, pour VueJS bientôt, pour iOS, etc.\nPour React c'est intéressant, on est capable de construire notre recherche\ncomplète, web et application native, et même bientôt server-side rendering avec\nune seule bibliothèque, sans avoir à mettre beaucoup de code entre les\ndifférents éléments de connexion. Pour l’utilisateur c'est super simplifié.\nFrank: Ça inclut React Native ?\nVincent Voyer : Oui, même si on n'a pas encore des widgets tout faits pour\nReact Native, on ne les a que pour le web, par contre on est capable de\nfacilement abstraire la création d’un menu de genre de films par exemple, si\nc'est un site de recherche de films.\nTout ça pour en venir où ? Tu parlais des générateurs de site statique. Tous ces\nsites-là, InstantSearch pour React, InstantSearch Vanilla, InstantSearch VueJS,\netc. ils ont des sites dédiés et ces sites-là on essaie de les faire du mieux\npossible, donc y’a une documentation complète, des guides, des tutoriels, un\npetit peu comme le site de documentation de React ou de Laravel, c'est vraiment\nun site complet où la personne va pouvoir y passer du temps et progresser au fur\net à mesure dans sa connaissance du produit.\nCes sites-là au fil du temps on les a fait évoluer. On est parti de\nJekyll, parce que le site d’Algolia c'est une appli\nRuby on Rails, du coup il y avait déjà des connaissances en Ruby. Au bout d’un\nmoment, on est arrivé aux limites de Jekyll et beaucoup de nos développeurs\nJavaScript voulaient travailler avec un outil en JavaScript. On a aussi essayé\nMiddleman. Donc on a des sites en Jekyll, en\nMiddleman, aujourd’hui on utilise MetalSmith, qui\nest un générateur assez bas niveau, très modulaire, qui nous a permis de créer\ndifférents petits modules pour sites statiques, par exemple extraire\nautomatiquement depuis le code la JSDoc, des pages\ndescriptives. Pour chaque widget InstantSearch par exemple c'est le cas : on\nprend la JSDoc avec des exemples, on transforme tout ça en une page Markdown et\nHTML qui décrit le widget. Idem pour d’autres parties de l’API, idem pour les\nguides.\nDonc aujourd’hui on utilise MetalSmith, et ça nous va bien, le seul truc c'est\nqu'on a pas encore fait un module qui abstrait tous les sites web, parce qu'en\nfait chaque site web est peu du copier-coller aujourd’hui, donc on aimerait bien\nfaire un truc qui nous permette de partager plus de code entre les différents\nsites web. Ça nous plait bien parce qu'à chaque fois qu'on a un besoin pour\nl’écrire c'est assez simple. Le flow de MetalSmith, il prend un répertoire\nsource et ensuite il applique des middleware qui font transformer les fichiers\nde ce répertoire source, donc on peut faire vraiment ce qu'on veut à partir du\nmoment où on sait écrire du JavaScript, c'est assez cool.\nFrank : 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 ?\nVincent Voyer : Nous on fait des sites super custom où on modifie vraiment\nbeaucoup la source, on n'a pas pris ces solutions là, on a peut-être loupé\nquelque chose, mais avec MetalSmith on est bien aujourd’hui. Mais les gens\npoussent pour essayer d’autres choses, donc on essaiera peut-être autre chose. À\npartir du moment où l’outil nous facilite la vie. Il y a clairement des choses\navec MetalSmith qui sont difficiles à faire, par exemple faire du bon\nlive-reload, c'est à nous de le faire. Ce qui est possible aussi c'est qu'avec\nnotre expérience avec MetalSmith, on se fasse nous-mêmes notre propre générateur\nde site statique pour Algolia.\nFrank : Dans le monde des sites statiques, on a beaucoup parlé de la refonte de Smashing Magazine 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\nVincent Voyer : Oui, on a des gens spécialisés en Go ici. En fait c'est\nvraiment en fonction des besoins, certaines personnes vont parfois venir sur\nnotre GitHub pour nous dire \"j'aimerais bien avoir un client d’API pour Rust\",\net là on va se dire OK on va le faire. On fait vraiment comme ça, Après parfois\non propose, mais seulement si nous en avons l’utilité en interne, parce que\nsinon on ne saura pas si le plugin est vraiment bien fait vu qu'on l’utilisera\npas.\nLe plugin Jekyll on l’a fait parce qu'on l’utilisait, le premier site\nInstantSearchJS, on utilisait le plugin\nalgoliasearch-jekyll pour\ntransférer les données de Jekyll vers Algolia. Après on a fait DocSearch, car\ntous ces sites web-là, comme ils ont plein de générateurs de site statique\ndifférents, nous on propose un niveau d’abstraction supplémentaire et on crawle\nà partir du HTML. Ce qui fonctionne aussi très bien et nous permet de ne pas\navoir à faire de plugins pour chaque générateur. Maintenant s'il y a quelqu'un\nqui veut un plugin Hugo ou Phenomic, du coup il faudra venir nous en parler et\non pourra faire.\nFrank : 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\nVincent Voyer : OK, je me le note.\nFrank : Algolia a levé 53 millions de dollars récemment, vous recrutez pas mal de gens, c'est quoi les prochaines priorités pour vous ?\nVincent Voyer : Notre ambition est d’être un acteur principal du monde de la\nrecherche, aujourd’hui il y a ElasticSearch qui est aussi un énorme acteur. Et\nle scope d’Algolia n'est pas terminé. Beaucoup de clients nous challengent sur\ndes choses qu'Algolia ne sait pas encore faire. Tous nos clients e-commerce\nveulent généralement avoir un contrôle plus précis sur la gestion des résultats,\ndes promotions, etc.\nAujourd’hui on n'a pas vraiment beaucoup d’outils pour faire ça, Algolia c'est\nvraiment un outil orienté pour les développeurs, donc c'est quelque chose sur\nlequel on travaille à fond, le fait de répondre encore plus finement aux besoins\ndes sites e-commerce et aux gens qui veulent faire du business sur les sites de\ne-commerce, qui veulent avoir des outils de contrôle des résultats de recherche.\nDonc on fait des évolutions sur les APIs pour permettre aux développeurs de\nconstruire des outils plus orientés business et e-commerce.\nSur tout ce qui est bibliothèque, InstantSearch c'est vraiment notre\nsous-produit phare de création d’interface de recherche, on en fait une vraie\nmarque. Le mot InstantSearch regroupe plein de sous-projets et ces projets on\nles fait évoluer et on en créé des nouveaux.\nJe t'ai parlé de VueJS, parce que c'est un framework qui est vraiment beaucoup\nutilisé, on va avoir potentiellement Angular s'il y a un énorme besoin Angular.\nToujours dans l’optique de dire qu'il faut que l’expérience de développement\nsoit la meilleure.\nSur une roadmap plus précise des évolutions d’Algolia, c'est assez compliqué, je\nsuis pas forcément le plus à même de t'en parler. Moi je travaille beaucoup sur\nInstantSearch par exemple, mais oui le moteur de recherche d’Algolia évolue\nénormément et va continuer d’évoluer énormément en fonction des besoins de nos\nclients qui nous challengent beaucoup dessus.\nOn travaille aussi beaucoup sur les datasets. Aujourd’hui Algolia sait bien\nrépondre à des jeux de plusieurs millions de données, qu'est-ce qui se passe\nquand on arrive à des centaines de millions ou à des milliards de données ? Ça\nc'est un gros challenge pour la plupart des moteurs de recherche et les bases de\ndonnées donc on est en train de travailler sur ça. C’est important pour pouvoir\npasser de 100 millions d’objets — ce qui est déjà énorme — à 800 millions ou 1\nmilliard.\nFrank : Toujours sur des données structurées ? Si j'ai des données non structurées, est-ce qu'Algolia peut faire quelque chose ?\nVincent Voyer : On travaille sur des outils qui vous nous permettre d’aller\nchercher des clients qui n'ont pas forcément des données structurées, mais dans\ntous les cas, ce qu'on fait c'est que ces outils-là vont permettre de\ntransformer ce qui n'est pas structuré en données structurées. Ça peut être un\noutillage en PHP pour pouvoir extraire, depuis une page HTML ou un article de\nblog sous WordPress par exemple, de la donnée structurée pour la faire rentrer\ndans le moteur d’Algolia, donc scinder en paragraphes, en titres, etc. Donc ça\nce sont des choses qu'on fait parce qu'effectivement, tout le monde n'a pas\nforcément une base de données structurée et pourtant ils ont quand même besoin\nd’un bon moteur de recherche. Mais au final on est convaincu qu'on n'ira pas\nvers ce que fait Google où on a des mots-clés en surbrillance dans les pages au\ncomplet. On essaiera de toujours extraire de la donnée structurée d’un site web,\ncar il y a toujours de la structure. Un article de blog, une page qui liste des\nfonctionnalités, tout ça a une structure, c'est simplement qu'il faut la\ntrouver.\nFrank : Et pour les données enfermées dans des tableurs, des fichiers PDF ?\nVincent Voyer : Même chose, ce sont des outils qui vont être capables\nd’aller chercher de la donnée et de l’indexer pour Algolia.\nDans les sujets qui nous intéressent, il y a aussi la recherche vocale. Moi-même\nje fais beaucoup de choses avec Siri sur mon téléphone, j'envoie des textos, je\nlance des recherches, etc. Et ça c'est quelque chose qu'on va devoir supporter,\nprévoir des fonctionnalités qui permettront aux gens de faire des recherches\navec la voix.\nOn a des projets qui tournent avec Alexa (la voix d’Amazon Echo). On peut\ndévelopper des skillsets qui vont permettre de dire à un client dans son\napplication Alexa de faire une recherche sur son site plus facilement. Nous,\nnotre responsabilité là-dedans, ça va être de se connecter de la bonne façon\ndans le moteur Alexa, définir comment on va scinder la phrase de l’utilisateur\net comment ça, ça va être appliqué à la recherche.\nFrank : Merci Vincent, merci pour toutes ces informations sur Algolia. C’est\nvraiment un chouette produit qui on l’a compris permet de fournir une recherche\nvraiment pertinente pour l’utilisateur. Pour en savoir plus rendez-vous sur\nAlgolia.com.",
      "content_html": "<aside class=\"note note-intro\"><p>Sur un site généré, l’interactivité est reléguée côté client, au navigateur,\ndonc à JavaScript. Si vous souhaitez intégrer une partie dynamique sur votre\nsite <em>statique</em>, il faudra donc faire appel à une API externe. C’est tout le\nconcept de la <a href=\"/2017/03/16/5-raisons-de-tester-la-jamstack/\">JAMStack</a>\ndont nous parlons sur ce blog. Et c'est typiquement le cas si vous souhaitez\nintégrer un moteur de recherche sur votre site généré. Heureusement pour nous,\nen ce qui concerne la recherche, <a href=\"https://www.algolia.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Algolia</a> propose une\nAPI et des bibliothèques qui vont faciliter l’intégration d’une recherche de\nqualité.<br>\nAvec une <a href=\"https://www.algolia.com/pricing\" target=\"_blank\" rel=\"noopener noreferrer\">formule community gratuite</a> qui\nautorise 10 000 enregistrements et 100 000 opérations par mois, c'est\nune solution intéressante à tester pour des sites statiques à trafic modeste. En\nproposant une personnalisation et une expérience utilisateur poussée, on peut\ndire qu'Algolia est ce qui se fait de mieux en la matière.<br>\nNous sommes donc allés interroger Vincent Voyer, ingénieur JavaScript fullStack\nchez Algolia pour qu'il nous en dise un peu plus sur ce service en pleine\ncroissance qui a réussi en l’espace de quelque temps à conquérir la communauté\ndes développeurs web.</p></aside>\n<p>Frank : <strong>Bonjour Vincent, on va peut-être commencer par te présenter ?</strong></p>\n<p><strong>Vincent Voyer</strong> : Donc moi je suis Vincent Voyer, sur Twitter j'étais zeroload\nmaintenant je suis <a href=\"https://twitter.com/vvoyer\" target=\"_blank\" rel=\"noopener noreferrer\">vvoyer</a>, je suis développeur\nJavaScript. Pendant un moment j'ai fait expert performance web, j'ai travaillé\nchez Fasterize dont j'étais cofondateur, j'ai travaillé chez Mappy, j'ai fait du\nconseil en développement freelance pour lemonde.fr, France Télévision…\nAujourd’hui je suis chez Algolia où je suis toujours développeur JavaScript. Je\ntravaille principalement sur des bibliothèques open source qui permettent\nd’intégrer Algolia sur tous les sites web.</p>\n<p>J'aime le basket, j'essaie d’inviter des co-workers toutes les semaines, c'est\ncool, ce soir on y va, on est trois plus le reste de l’équipe. J'ai 32 ans, une\nfemme et un enfant. Voilà.</p>\n<p>Frank : <strong>T'as l’air de bien t'éclater, qu'est-ce qui te plaît chez Algolia ?</strong></p>\n<p><strong>Vincent Voyer</strong> : J'ai une petite anecdote à ce sujet. Quand j'étais\ndéveloppeur freelance, je suis allé à DotJS, il y avait un stand Algolia, je\nsuis simplement passé devant. Après il y avait une conférence d’un gars qui\ns'appelle <a href=\"https://twitter.com/Substack\" target=\"_blank\" rel=\"noopener noreferrer\">Substack</a>, James Halliday, un gros\ndéveloppeur JavaScript de la communauté npm, qui disait que la recherche sur le\nsite de npm n'était pas super, quand on met deux mots ça fait un ET et pas un\nOU, il n'y a pas de tolérance aux fautes de frappe, c'est pas super.</p>\n<p>Je repasse devant le stand Algolia et là il y avait une énergie, il y avait\nAlexandre Colin, qui était Solution Engineer à cette époque-là, qui présentait\nAlgolia. Ils me disent qu'ils recrutent. Je me dis je pourrais faire un petit\ntest avec Algolia pour faire un moteur de recherche de paquets npm.</p>\n<p>Du coup j'ai fait ça, en fin d’année 2014, un petit test de recherche de paquets\nnpm : j'ai pris 300 000 paquets npm, j’ai créé un index de recherche\nAlgolia avec. J'ai commencé à faire les premières requêtes, ça prenait 4\nmillisecondes.</p>\n<p>J'étais capable de faire une recherche dans un champ de recherche, d’exécuter\nune requête, élaborée à l’aide du framework Express et d’obtenir des résultats\nen temps réel en 4ms en incluant le temps de latence réseau, etc. 4ms, 10ms, je\nme suis dit : wouah c'est quand même incroyable !</p>\n<p>Voilà, ma première rencontre avec Algolia c'était ça. Et très vite après j'ai\nété en contact avec eux, car je voulais refaire leur client JavaScript, qui ne\nme plaisait pas en tant que développeur JavaScript…</p>\n<p>Frank : <strong>Comment définirais-tu l’activité d’Algolia ?</strong></p>\n<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\nAPI qui permet de créer un index de recherche pour son propre site web. Par\nexemple si vous avez un site web e-commerce avec 100 000 produits. Pour\nfaire votre moteur de recherche : soit vous le faites vous-même, ou vous prenez\nElasticSearch, etc. Du coup, vous devez gérer l’infrastructure, etc. Soit vous\noptez pour une solution Saas comme Algolia, qui vous permet de mettre tous vos\nproduits dans un index de recherche et qui vous expose une API pour ajouter ou\nmodifier des produits mais aussi pour chercher vos produits.</p>\n<p>Aujourd’hui les gros clients d’Algolia dont on peut parler c'est Stripe, Strava,\n8tracks, CrunchBase, Periscope, Twitch, etc. Donc c'est des gros noms, ça marche\nplutôt bien. Algolia existe depuis cinq ans maintenant.</p>\n<p>Frank : <strong>Pourquoi est-ce que le moteur de recherche d’Algolia est aussi rapide aujourd’hui ?</strong></p>\n<p><strong>Vincent Voyer</strong> : C’est parce qu'au début c'était un SDK pour développeurs\nd’applications mobiles, pour les aider à mettre un champ de recherche dans leur\napplication mobile sur les données des utilisateurs. Et comme il fallait que ce\nSDK tourne sur tous les types de mobiles, que ça soit un vieux smartphone Nokia\nou le dernier iPhone, du coup tout le code était optimisé pour que ce soit\nvraiment rapide à tout moment, donc optimisé à la milliseconde près.</p>\n<p>Ce produit-là n'a pas fonctionné et quand ils ont fait Ycombinator, un\nincubateur de startups aux États-Unis, ils leur ont dit : \"C’est pas ça qu'il\nfaut faire en fait, c'est une API Web\". Ils ont fait ça et ça a fonctionné et\naujourd’hui c'est notre principal produit, l’API et le moteur de recherche\nd’Algolia.</p>\n<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>\n<p><strong>Vincent Voyer</strong> : Oui c'est ça. Généralement votre site de e-commerce, il a\nune base de données avec des produits structurés. Beaucoup de gens passent par\nGoogle pour rechercher des produits. Ils vont chercher par exemple \"FNAC +\niPhone\" sur Google. Le truc c'est qu'ils vont arriver sur le site de la FNAC et\nsi ça se trouve, ils vont vouloir faire une nouvelle recherche. Donc là sur le\nsite de la FNAC il y a un champ recherche et ce champ de recherche là il faut\nqu'il soit beau. Ce que propose Google c'est une recherche dans l’Internet au\nglobal, mais ils ne proposent pas d’implémenter un bon moteur de recherche pour\nson site web. En fait ils le proposaient, ça s'appelait\n<a href=\"https://enterprise.google.com/search/products/gss.html\" target=\"_blank\" rel=\"noopener noreferrer\">Google Site Search</a>,\nils proposaient aux éditeurs de site web de mettre un champ de recherche avec\ndes pages de résultats dédiées au site en cours, c'est pas les résultats dans le\nmonde. Mais c'est pas suffisant.</p>\n<p>Nous ce qu'on a voulu proposer c'est qu'on sait qu'un site de e-commerce il a\nune base de données avec des objets structurés et on s'est rendu compte que pour\nfournir une bonne expérience de recherche, avec de la tolérance à la faute de\nfrappe, avec du filtrage par type d’objet, il fallait pouvoir connaître la\nstructure d’un objet. Il faut savoir qu'une page produit, elle a une catégorie,\npour pouvoir proposer aux gens de filtrer sur cette catégorie-là. Du coup Google\naujourd’hui il ne sait pas faire ça, il essaie de déterminer en fonction de ses\nalgorithmes ce dont parle une page, mais dans les résultats Google on va pas\npouvoir dire : \"je veux tous les iPhone\", on va pouvoir l’exprimer avec une\nrequête, mais s'il le faut, cette requête elle va nous renvoyer des iPhone, des\ncoques d’iPhone, des livres sur l’iPhone, etc. Nous ce qu'on propose c'est des\nobjets structurés qui permettent ensuite aux développeurs de site web de créer\ndes menus, des sliders pour filtrer, etc.</p>\n<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>\n<p><strong>Vincent Voyer</strong> : Les gens s'attendent aujourd’hui à avoir la meilleure\nexpérience de recherche, qui est celle de Google. Et Google ils ont fait un truc\nqui s'appelle <em>Instant Search</em>, qui affiche des propositions au fur et à mesure\nqu'on tape notre requête. Donc déjà tous les moteurs de recherche ont dû se\nbaser sur ça et proposer le <em>Search as you Type</em>. C’est super important car ça\npermet à la personne de ne pas avoir à taper \"iPhone\" puis de taper sur la\ntouche Entrée, se rendre compte qu'elle veut changer, revenir en arrière, etc.\nLà c'est au fur et à mesure, c'est naturel. Ça, ça vient aussi du fait que ça a\nété développé sur mobile à la base, car c'est encore plus compliqué de faire des\nworkflows de navigation sur mobile, du coup sur mobile il faut que ce soit le\nplus simple possible. L'autre paramètre qui a été pris en compte c'est la\ntolérance aux fautes de frappe.</p>\n<p>Toutes ces choses-là, il y a des moteurs de recherche qui les font, mais chez\nAlgolia on s'est rendu compte qu'il n'y en avait aucun qui remplissait tous les\ncritères : une bonne performance, la gestion des fautes de frappe, du faceting\navancé et des résultats personnalisés, à savoir la capacité à dire : j'ai une\nliste d’iPhone, mais je veux en faire remonter certains dans les résultats. Par\nexemple sur la requête \"iPhone\" je vais vouloir favoriser certains résultats.\nPar exemple je ne veux pas que ce soit les coques d’iPhone qui apparaissent en\npremier, je veux que ce soit les iPhone. Nous on a par exemple un <em>custom\nranking</em> qui fait que ce sont les produits les plus consultés qui vont remonter\nde façon naturelle. Au fur et à mesure que les gens vont sur la page iPhone, on\nva mettre l’index de recherche à jour et l’affichage des résultats va être\nnaturel par rapport à ce que les gens demandent.</p>\n<p>Algolia propose donc toutes ces fonctionnalités, performance, typo tolérance,\nfaceting et recherche avancés, que ne proposent pas forcément les autres moteurs\nde recherche.</p>\n<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>\n<p><strong>Vincent Voyer</strong> : La stratégie d’Algolia c'est d’avoir une super bonne\n<em>Developer Experience</em> et d’essayer de capter un maximum de développeurs sur\nnotre plate-forme forcément, pour que tout le monde soit au courant qu'on est un\nsuper moteur de recherche. Sachant que nous-mêmes nous sommes développeurs, et\nque nos outils c'est <code>npm</code>, c'est <code>yarn</code>, ça va être Hacker News, les différents\nframeworks PHP comme Laravel ou Symfony, React pour le monde JavaScript. Tout ça\nce sont nos outils de tous les jours.</p>\n<p>Et on a fait un jour le constat que sur tous ces sites-là, c'est souvent\ndifficile de trouver l’information qu'on cherche. Donc on a fait un projet qui\ns'appelle <a href=\"https://community.algolia.com/docsearch/\" target=\"_blank\" rel=\"noopener noreferrer\">DocSearch</a>, qui est en gros\nun crawler de documentation pour les développeurs. DocSearch fournit aujourd’hui\nde la recherche pour les développeurs sur plus de 300 sites web. Donc quand vous\ncherchez sur le site web de React, de Flow, de Symfony, de Laravel, en fait\nc'est Algolia qui est derrière. On l’affiche, c'est pour ça qu'on rend ce\nservice gratuitement, c'est parce que derrière ça va faire parler de nous et ça\nnous permet à nous en interne d’être plus efficaces.</p>\n<p>En faisant cela, nous avons pollinisé d’autres communautés et plein d’autres\nsites ce sont dit : \"Hé, je veux la même recherche, c'est super !\"</p>\n<p>Donc on a construit au fur et à mesure ces différents projets.</p>\n<p>Frank: <strong>Comment ça fonctionne ?</strong></p>\n<p><strong>Vincent Voyer</strong> : DocSearch c'est un crawler de site web avec des\nconfigurations par site. Pour chaque site on va dire : le nom d’une méthode dans\nl’API, c'est dans cette balise HTML, etc. Ça ressemble un petit peu à ce que\nfait Google, mais là c'est fait de façon contrôlée, c'est pas un algorithme\ngénérique, on cible exactement ce à quoi on veut que ressemble la documentation.</p>\n<p>Donc on a fait ça, c'était un des premiers gros trucs pour la communauté des\ndéveloppeurs, et à côté de ça on a aussi tout ce qui est outillage qu'on fournit\naux développeurs qui utilisent Algolia.</p>\n<p>Ce dont on a envie c'est qu'à aucun moment ils se disent \"Algolia ça a l’air\nd’être un super moteur de recherche, mais c'est juste une API, y’a rien\nderrière, du coup je suis obligé de tout construire. Si j'utilise Symfony, il va\nfalloir que je développe mon propre connecteur Symfony-Algolia, si j'utilise\nJekyll pareil, si j'utilise React il va falloir que je fasse mes propres\ncomposants qui vont faire la liaison entre des composants React et la recherche\nsur Algolia.\"</p>\n<p>Du coup c'est ce qu'on a fait au fur et à mesure, pour tous les langages de\nprogrammation les plus utilisés. On a 14 clients d’API je crois (JavaScript,\nRuby, Python, Go, etc.).</p>\n<p>Ce qui permet à chaque développeur de ne pas avoir à implémenter son propre\nclient d’API qui se base sur notre API. On les a vraiment créés pour eux. La\nplupart des boîtes s'arrêtent à trois et ils se disent pour les autres \"c'est la\ncommunauté qui les fera à un certain moment\". Ça marche ou ça ne marche pas, la\nqualité est là ou pas. Nous on les a faits pour tout le monde, tout est open\nsource donc les gens participent.</p>\n<p>Et après on est allé encore plus loin, car les clients d’API c'est assez bas\nniveau, sachant que sur un site web ce que je veux c'est une page de recherche\navec un champ recherche, avec une navigation, un slider sur le prix, etc.</p>\n<p>Donc on a créé des bibliothèques plus haut niveau qui permettent d’exprimer ça\npour le développeur. Il est capable à l’aide de widgets de dire \"là je mets un\nchamp de recherche, là je mets un sélecteur sur les genres de films, là je mets\nun slider sur l’année de sortie du film\", ce genre de choses.</p>\n<p>Tout ça il peut l’exprimer à l’aide\nd’<a href=\"https://community.algolia.com/instantsearch.js/v2/\" target=\"_blank\" rel=\"noopener noreferrer\">InstantSearch</a>, qui est\nun projet qui englobe des sous-projets. InstantSearch c'est un ensemble de\nbibliothèques haut-niveau pour implémenter de la recherche avec Algolia.</p>\n<p>Frank: <strong>C’est complémentaire avec DocSearch dont tu parlais tout à l’heure ?</strong></p>\n<p><strong>Vincent Voyer</strong> : Non, ce sont deux produits différents, on a plein de\nproduits qu'on essaie de regrouper au fur et à mesure. InstantSearch c'est\nvraiment pour les clients d’Algolia généralement qui vont vouloir implémenter de\nla recherche sur leur application React ou VueJS par exemple, de façon plus\nsimple que de devoir faire des appels à l’API d’Algolia qui est très puissante,\ncertes, mais elle est puissante parce qu'elle est modulaire, donc nous on cache\nun peu cette modularité pour répondre à la plupart des cas d’utilisation qu'ont\nles gens, tout en laissant la porte ouverte pour des trucs un petit peu plus\navancés qui seront faits avec des paramètres différents.</p>\n<p>InstantSearch c'est disponible pour du Vanilla JavaScript, C’est-à-dire sans\nframework, pour React, pour VueJS bientôt, pour iOS, etc.</p>\n<p>Pour React c'est intéressant, on est capable de construire notre recherche\ncomplète, web et application native, et même bientôt server-side rendering avec\nune seule bibliothèque, sans avoir à mettre beaucoup de code entre les\ndifférents éléments de connexion. Pour l’utilisateur c'est super simplifié.</p>\n<p>Frank: <strong>Ça inclut React Native ?</strong></p>\n<p><strong>Vincent Voyer</strong> : Oui, même si on n'a pas encore des widgets tout faits pour\nReact Native, on ne les a que pour le web, par contre on est capable de\nfacilement abstraire la création d’un menu de genre de films par exemple, si\nc'est un site de recherche de films.</p>\n<p>Tout ça pour en venir où ? Tu parlais des générateurs de site statique. Tous ces\nsites-là, InstantSearch pour React, InstantSearch Vanilla, InstantSearch VueJS,\netc. ils ont des sites dédiés et ces sites-là on essaie de les faire du mieux\npossible, donc y’a une documentation complète, des guides, des tutoriels, un\npetit peu comme le site de documentation de React ou de Laravel, c'est vraiment\nun site complet où la personne va pouvoir y passer du temps et progresser au fur\net à mesure dans sa connaissance du produit.</p>\n<p>Ces sites-là au fil du temps on les a fait évoluer. On est parti de\n<a href=\"https://jekyllrb.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Jekyll</a>, parce que le site d’Algolia c'est une appli\nRuby on Rails, du coup il y avait déjà des connaissances en Ruby. Au bout d’un\nmoment, on est arrivé aux limites de Jekyll et beaucoup de nos développeurs\nJavaScript voulaient travailler avec un outil en JavaScript. On a aussi essayé\n<a href=\"https://middlemanapp.com\" target=\"_blank\" rel=\"noopener noreferrer\">Middleman</a>. Donc on a des sites en Jekyll, en\nMiddleman, aujourd’hui on utilise <a href=\"http://www.metalsmith.io/\" target=\"_blank\" rel=\"noopener noreferrer\">MetalSmith</a>, qui\nest un générateur assez bas niveau, très modulaire, qui nous a permis de créer\ndifférents petits modules pour sites statiques, par exemple extraire\nautomatiquement depuis le code la <a href=\"http://usejsdoc.org/\" target=\"_blank\" rel=\"noopener noreferrer\">JSDoc</a>, des pages\ndescriptives. Pour chaque widget InstantSearch par exemple c'est le cas : on\nprend la JSDoc avec des exemples, on transforme tout ça en une page Markdown et\nHTML qui décrit le widget. Idem pour d’autres parties de l’API, idem pour les\nguides.</p>\n<p>Donc aujourd’hui on utilise MetalSmith, et ça nous va bien, le seul truc c'est\nqu'on a pas encore fait un module qui abstrait tous les sites web, parce qu'en\nfait chaque site web est peu du copier-coller aujourd’hui, donc on aimerait bien\nfaire un truc qui nous permette de partager plus de code entre les différents\nsites web. Ça nous plait bien parce qu'à chaque fois qu'on a un besoin pour\nl’écrire c'est assez simple. Le flow de MetalSmith, il prend un répertoire\n<code>source</code> et ensuite il applique des middleware qui font transformer les fichiers\nde ce répertoire <code>source</code>, donc on peut faire vraiment ce qu'on veut à partir du\nmoment où on sait écrire du JavaScript, c'est assez cool.</p>\n<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>\n<p><strong>Vincent Voyer</strong> : Nous on fait des sites super custom où on modifie vraiment\nbeaucoup la source, on n'a pas pris ces solutions là, on a peut-être loupé\nquelque chose, mais avec MetalSmith on est bien aujourd’hui. Mais les gens\npoussent pour essayer d’autres choses, donc on essaiera peut-être autre chose. À\npartir du moment où l’outil nous facilite la vie. Il y a clairement des choses\navec MetalSmith qui sont difficiles à faire, par exemple faire du bon\nlive-reload, c'est à nous de le faire. Ce qui est possible aussi c'est qu'avec\nnotre expérience avec MetalSmith, on se fasse nous-mêmes notre propre générateur\nde site statique pour Algolia.</p>\n<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>\n<p><strong>Vincent Voyer</strong> : Oui, on a des gens spécialisés en Go ici. En fait c'est\nvraiment en fonction des besoins, certaines personnes vont parfois venir sur\nnotre GitHub pour nous dire \"j'aimerais bien avoir un client d’API pour Rust\",\net là on va se dire OK on va le faire. On fait vraiment comme ça, Après parfois\non propose, mais seulement si nous en avons l’utilité en interne, parce que\nsinon on ne saura pas si le plugin est vraiment bien fait vu qu'on l’utilisera\npas.</p>\n<p>Le plugin Jekyll on l’a fait parce qu'on l’utilisait, le premier site\nInstantSearchJS, on utilisait le plugin\n<a href=\"https://github.com/algolia/algoliasearch-jekyll\" target=\"_blank\" rel=\"noopener noreferrer\">algoliasearch-jekyll</a> pour\ntransférer les données de Jekyll vers Algolia. Après on a fait DocSearch, car\ntous ces sites web-là, comme ils ont plein de générateurs de site statique\ndifférents, nous on propose un niveau d’abstraction supplémentaire et on crawle\nà partir du HTML. Ce qui fonctionne aussi très bien et nous permet de ne pas\navoir à faire de plugins pour chaque générateur. Maintenant s'il y a quelqu'un\nqui veut un plugin Hugo ou Phenomic, du coup il faudra venir nous en parler et\non pourra faire.</p>\n<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>\n<p><strong>Vincent Voyer</strong> : OK, je me le note.</p>\n<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>\n<p><strong>Vincent Voyer</strong> : Notre ambition est d’être un acteur principal du monde de la\nrecherche, aujourd’hui il y a ElasticSearch qui est aussi un énorme acteur. Et\nle scope d’Algolia n'est pas terminé. Beaucoup de clients nous challengent sur\ndes choses qu'Algolia ne sait pas encore faire. Tous nos clients e-commerce\nveulent généralement avoir un contrôle plus précis sur la gestion des résultats,\ndes promotions, etc.</p>\n<p>Aujourd’hui on n'a pas vraiment beaucoup d’outils pour faire ça, Algolia c'est\nvraiment un outil orienté pour les développeurs, donc c'est quelque chose sur\nlequel on travaille à fond, le fait de répondre encore plus finement aux besoins\ndes sites e-commerce et aux gens qui veulent faire du business sur les sites de\ne-commerce, qui veulent avoir des outils de contrôle des résultats de recherche.</p>\n<p>Donc on fait des évolutions sur les APIs pour permettre aux développeurs de\nconstruire des outils plus orientés business et e-commerce.</p>\n<p>Sur tout ce qui est bibliothèque, InstantSearch c'est vraiment notre\nsous-produit phare de création d’interface de recherche, on en fait une vraie\nmarque. Le mot InstantSearch regroupe plein de sous-projets et ces projets on\nles fait évoluer et on en créé des nouveaux.</p>\n<p>Je t'ai parlé de VueJS, parce que c'est un framework qui est vraiment beaucoup\nutilisé, on va avoir potentiellement Angular s'il y a un énorme besoin Angular.\nToujours dans l’optique de dire qu'il faut que l’expérience de développement\nsoit la meilleure.</p>\n<p>Sur une roadmap plus précise des évolutions d’Algolia, c'est assez compliqué, je\nsuis pas forcément le plus à même de t'en parler. Moi je travaille beaucoup sur\nInstantSearch par exemple, mais oui le moteur de recherche d’Algolia évolue\nénormément et va continuer d’évoluer énormément en fonction des besoins de nos\nclients qui nous challengent beaucoup dessus.</p>\n<p>On travaille aussi beaucoup sur les datasets. Aujourd’hui Algolia sait bien\nrépondre à des jeux de plusieurs millions de données, qu'est-ce qui se passe\nquand on arrive à des centaines de millions ou à des milliards de données ? Ça\nc'est un gros challenge pour la plupart des moteurs de recherche et les bases de\ndonnées donc on est en train de travailler sur ça. C’est important pour pouvoir\npasser de 100 millions d’objets — ce qui est déjà énorme — à 800 millions ou 1\nmilliard.</p>\n<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>\n<p><strong>Vincent Voyer</strong> : On travaille sur des outils qui vous nous permettre d’aller\nchercher des clients qui n'ont pas forcément des données structurées, mais dans\ntous les cas, ce qu'on fait c'est que ces outils-là vont permettre de\ntransformer ce qui n'est pas structuré en données structurées. Ça peut être un\noutillage en PHP pour pouvoir extraire, depuis une page HTML ou un article de\nblog sous WordPress par exemple, de la donnée structurée pour la faire rentrer\ndans le moteur d’Algolia, donc scinder en paragraphes, en titres, etc. Donc ça\nce sont des choses qu'on fait parce qu'effectivement, tout le monde n'a pas\nforcément une base de données structurée et pourtant ils ont quand même besoin\nd’un bon moteur de recherche. Mais au final on est convaincu qu'on n'ira pas\nvers ce que fait Google où on a des mots-clés en surbrillance dans les pages au\ncomplet. On essaiera de toujours extraire de la donnée structurée d’un site web,\ncar il y a toujours de la structure. Un article de blog, une page qui liste des\nfonctionnalités, tout ça a une structure, c'est simplement qu'il faut la\ntrouver.</p>\n<p>Frank : <strong>Et pour les données enfermées dans des tableurs, des fichiers PDF ?</strong></p>\n<p><strong>Vincent Voyer</strong> : Même chose, ce sont des outils qui vont être capables\nd’aller chercher de la donnée et de l’indexer pour Algolia.</p>\n<p>Dans les sujets qui nous intéressent, il y a aussi la recherche vocale. Moi-même\nje fais beaucoup de choses avec Siri sur mon téléphone, j'envoie des textos, je\nlance des recherches, etc. Et ça c'est quelque chose qu'on va devoir supporter,\nprévoir des fonctionnalités qui permettront aux gens de faire des recherches\navec la voix.</p>\n<p>On a des projets qui tournent avec Alexa (la voix d’Amazon Echo). On peut\ndévelopper des <em>skillsets</em> qui vont permettre de dire à un client dans son\napplication Alexa de faire une recherche sur son site plus facilement. Nous,\nnotre responsabilité là-dedans, ça va être de se connecter de la bonne façon\ndans le moteur Alexa, définir comment on va scinder la phrase de l’utilisateur\net comment ça, ça va être appliqué à la recherche.</p>\n<p><strong>Frank</strong> : Merci Vincent, merci pour toutes ces informations sur Algolia. C’est\nvraiment un chouette produit qui on l’a compris permet de fournir une recherche\nvraiment pertinente pour l’utilisateur. Pour en savoir plus rendez-vous sur\n<a href=\"https://algolia.com\" target=\"_blank\" rel=\"noopener noreferrer\">Algolia.com</a>.</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/06/07/migration-de-jekyll-a-hugo/",
      "url": "https://jamstatic.fr/2017/06/07/migration-de-jekyll-a-hugo/",
      "title": "Passer de Jekyll+Github Pages à Hugo+Netlify",
      "summary": "Le détail de la migration du blog de Sara Soueidan de Jekyll à Hugo et son passage de GitHub Pages à Netlify.",
      "date_published": "2017-06-07T00:00:00+00:00","content_text": "Si vous faites du développement front-end, du CSS, du SVG et\nautres joyeusetés, vous connaissez sans doute déjà la talentueuse\nSara Soueidan. Il se trouve que Sara a travaillé\nrécemment sur la refonte de Smashing Magazine et la migration de WordPress à\nHugo. Cette mission lui\na permis de se familiariser avec Hugo et de découvrir au passage le service\noffert par Netlify, la nouvelle référence en termes\nd’hébergement d’applications statiques. Fatiguée des faibles temps de\ncompilation proposés par Jekyll (dus en grande partie à la lenteur de Kramdown,\nle parseur Markdown utilisé, et à ses traitements d’expressions régulières),\nSara en a profité pour s'attaquer à la migration de son site perso. Comme\nbeaucoup d’autres, elle a été immédiatement séduite par les performances\nproposées par Hugo, le générateur statique ultra-rapide et ultra-souple écrit en\nGo. Elle nous livre ici en détails le récit de cette migration qu'elle est bien\ncontente d’avoir menée à bien. Puisse le partage de son périple vous épargner de\nsubir les mêmes écueils et vous aider à commencer à vous familiariser avec les\nconcepts d’Hugo.\nCes derniers mois, travailler sur mon site web s'est révélé être de plus en plus\npénible, que ce soit pour continuer à le développer, itérer sur son design,\nécrire un article de blog ou mettre à jour mes pages conférences et ateliers.\nC'était dû en partie à Jekyll, le générateur de site\nstatique que j'utilisais alors. Le vent du changement commençait à souffler.\nJekyll était devenu incroyablement lent et chaque changement entraîne une\nrecompilation… C'était devenu tellement lent qu'attendre que la compilation du\nsite soit terminée est devenue une vraie torture, tellement chronophage qu'il\nfallait que je m'en débarrasse à tout prix.\nOn pourrait croire que j'exagère, mais je vous promets que non. Jekyll est\ndevenu beaucoup trop lent. \"Trop lent\" est en réalité un euphémisme.\nDernièrement chaque fois que je modifiais une propriété CSS ou que j'effectuais\nune modification du code HTML je devais attendre jusqu'à cinq minutes pour que\nle changement soit pris en compte et compilé par Jekyll. Encore une fois je\nn'exagère pas. Jekyll se figeait littéralement. Il fallait que je l’arrête en\nfaisant CTRL-C pour débloquer la situation et que je le relance pour que les\nchangements soient pris en compte et qu'il finisse par compiler. Et si\nj'effectuais beaucoup de changements d’affilée, le ventilateur de mon Macbook\ncommençait à s'emballer comme un fou, l’ordinateur chauffait et faisait le bruit\nd’un avion sur le point de décoller. 1\nJe dirais que mon site est de taille modeste. J'ai moins d’une centaine de\nbillets de blog, même moins de 60 à l’heure où j'écris cet article, et seulement\nquelques pages statiques. Je ne me repose pas beaucoup sur JavaScript. En fait,\nj'ai à peine besoin d’utiliser la moindre ligne de JavaScript. Et pourtant,\nJekyll avait du mal à chaque fois qu'il devait compiler.\nOui, j'ai utilisé des options comme --incremental et toutes celles que l’on\nm'a recommandées pour accélérer le processus de compilation. Sans le moindre\nrésultat.\nJ'ai même du mal à souligner à quel point cela a empiré ces douze derniers mois.\nJe ressentais littéralement une montée d’hormones de stress dans mon flux\nsanguin à chaque fois que j'imaginais que je devais apporter un changement à mon\nsite Web. Je savais que j'allais vivre un enfer en faisant cela.\nMais je savais que cela ne pourrait durer éternellement. Je savais que je\ndevrais laisser tomber Jekyll et migrer à un moment donné vers un nouveau\ngénérateur. Je n'ai simplement jamais eu le temps de le faire. Pour être\nhonnête, je n'ai jamais vraiment pris le temps de le faire, car à chaque fois\nque j'avais du temps de libre je voulais profiter au maximum de ce temps en\nrestant éloignée de mon ordinateur. Mon site n'était simplement pas une\npriorité, surtout que j'étais encore indécise sur l’alternative à utiliser. Du\ncoup j'ai continué de le laisser en l’état.\nMais dernièrement, sachant que j'avais quelques semaines de libres pour faire ce\nque je voulais et comme j'ai commencé à avoir plein d’idées pour mon blog et que\nje voulais vraiment qu'elles voient le jour, comme mettre en place une lettre\nd’information, modifier le design, améliorer le code (un travail toujours en\ncours), ajouter un nouveau type de contenu (c'est pour bientôt) et quelques\nautres idées, j'ai finalement réussi à m'y mettre et à le faire, car je\nvoulais que mes idées prennent forme et écrire quelques billets de blog.\nMais d’abord j'avais besoin de pouvoir de nouveau prendre du plaisir à\ntravailler sur mon site web. Du coup je me suis dit : \"Ça suffit, je vais\ndevoir m'y mettre pour de bon pendant quelques jours cette semaine et dédier du\ntemps à passer à un nouveau générateur de site statique\". Je savais que c'était\nun investissement personnel nécessaire et extrêmement utile que je devais me\nrésoudre à faire. Je m'y suis investie et je l’ai fait. (C’est vraiment la\nmanière la plus efficace d’être productif : le faire.)\nChoisir un générateur de site statique\nComme je l’ai dit plus haut, une des raisons pour laquelle je n'ai pas changé de\ngénérateur plus tôt c'est parce que je ne savais pas lequel je voulais utiliser.\nPlusieurs personnes sur Twitter m'ont gentiment suggéré quelques-unes des\nnombreuses options disponibles. Mais aucune de ces options ne m'allait.\nVoyez-vous, chaque personne a son propre mode de fonctionnement et ses\npréférences lorsqu'il s'agit d’organiser ses fichiers, ses dossiers et son\ntravail. Aucun des générateurs statiques que j'ai regardés n'avait ce que je\nrecherchais pour mon site. Jusqu'à ce que quelqu'un me suggère de jeter un œil à\nHugo.\nJ'ai passé la documentation en revue quelques minutes, simplement pour me faire\nune idée de ce à quoi je pouvais m'attendre et de ce que Hugo avait à offrir –\nhistoire d’avoir une première impression, à proprement parler. Après avoir lu la\npartie sur la structuration des contenus et leur organisation et appris comment\nHugo offre la possibilité de créer plein de sections et de catégories de\ncontenus différents, en plus de la souplesse générale qu'il procure, je me suis\ndit que c'était le générateur de site statique dont j'avais toujours rêvé et\ncelui dont j'avais besoin. L'organisation et la structure ressemblait exactement\nà ce que j'avais pu imaginer pour mon propre site.\nMais ce qui m'a fait adopté Hugo plus que toute autre option, c'est de voir\nà quel point il est rapide\ncomparé à Jekyll. Non seulement chaque billet de blog que j'ai pu lire y est\nallé d’une comparaison qui atteste ce fait, mais j'ai aussi pu faire\nl’expérience de cette vitesse pour la première fois lorsque j'ai travaillé sur\nla refonte de Smashing Magazine.\nLa nouvelle version de Smashing Magazine (actuellement accessible via\nnext.smashingmagazine.com) utilise Hugo\ncomme générateur de site statique. La configuration que j'ai utilisée lorsque je\nmontais le front-end du magazine s'est montrée tellement rapide que je n'avais\naucun doute quant à la véracité des résultats que je pouvais lire. Et comme mon\nsite est bien plus petit que Smashing Magazine, je savais que je n'avais aucun\nsouci à me faire. Si Smashing Magazine pouvait être compilé aussi rapidement,\npourquoi pas mon blog ?\nVeuillez prendre note que cet article n'est en aucun cas\ndestiné à constituer un guide exhaustif sur Hugo. Il me reste encore beaucoup de\nchoses à comprendre, je suis donc mal placée pour écrire un tel guide. Vous\nverrez que vous devrez vous reporter à la documentation d’Hugo pour en savoir\nplus sur les sujets que je vais aborder. Prenez cet article comme un guide qui\npeut vous aider à savoir par où commencer (et parfois savoir quoi faire) sur\ncertaines thématiques particulières propres à Hugo. Et ce n'est en fin de compte\npas une comparaison entre Hugo et Jekyll. C’est davantage une introduction à\nHugo qui comporte quelques astuces. Si vous envisagez d’adopter Hugo comme\nnouveau générateur de site statique, j'espère que vous trouverez quelques trucs\nutiles pour avoir un système fonctionnel.\nConfigurer Hugo\nConfigurer Hugo n'est pas compliqué. Il y a deux guides dans la documentation :\nun pour\ninstaller Hugo sur un Mac et\nun pour\nl’installer sur Windows.\nDans cet article je ferai toujours référence à la configuration pour un Mac,\npuisque c'est ma principale machine de travail.\nJ'ai utilisé brew pour installer Hugo :\nbrew install hugo\nJ'ai suivi les instructions présentes sur la page d’installation, mis à jour\nbrew et lancé quelques commandes pour m'assurer que tout était bien installé\net fonctionnait correctement. C’est tout ce dont vous avez besoin pour qu'Hugo\ntourne sur votre machine. Difficile de faire plus simple. Avec Jekyll, ce\nn'était pas aussi indolore, je me rappelle avoir passé pas mal de temps à le\nconfigurer pour le faire tourner à l’époque.\nJ'ai parfois tendance à être une développeuse paresseuse. Mais ça a du bon, car\ncela me pousse à trouver la manière la plus rapide et la plus simple de mener à\nbien une tâche. Et donc la première des choses que j'ai voulu faire a été de\nmigrer automatiquement tous mes articles de blog dans Hugo sans avoir à repasser\nsur chacun des billets pour modifier le front matter. (J'aurais\nvraisemblablement abandonné si j'avais dû faire cela 😅)\nHeureusement, depuis la version 0.15, Hugo offre\nune commande pour migrer depuis Jekyll.\nVous n'avez qu'à taper la ligne suivante dans le terminal – en remplaçant\nchemin_site_jekyll et repertoire_destination par les chemins vers le\nrépertoire utilisé actuellement pour votre site sous Jekyll et celui dans lequel\nvous voulez configurer votre nouveau site – et Hugo se chargera d’importer les\nfichiers de votre installation actuelle de Jekyll dans le répertoire qui\ncontiendra votre site Hugo :\nhugo import jekyll chemin_site_jekyll repertoire_destination\nSi vous n'importez pas un site depuis Jekyll, vous pouvez toujours aller lire la\ndocumentation qui détaille ce qu'il faut savoir sur la structure des répertoires\nde Hugo, où ranger les assets, le contenu, les modèles de mise en page et bien\nplus.\nL'étape suivante consiste à convertir vos modèles Jekyll en modèles Hugo et\nc'est là où réside la plus grande partie du travail et où je me suis arrachée\nles cheveux pas mal de fois. Mais croyez-moi, le résultat final prouve que ça\nvalait vraiment le coup. Au passage, j'ai beaucoup appris. C’est ce que je\nvais partager avec vous dans la prochaine section.\nAstuce : Vous appartenez peut-être à une autre catégorie\nde développeur fainéant, vous préférez peut-être partir d’un modèle standard qui\nvous fournit la configuration dont vous avez besoin et qui est prêt pour que\nvous puissiez ajouter du contenu sur le champ, surtout si vous démarrez un\nnouveau blog. Dans ce cas je vous recommande chaudement le\nmodèle Victor Hugo de Netlify, qui\ncontient tout ce qu'il faut, il y a même Webpack et Gulp de correctement\nconfigurés pour pouvoir faire tourner votre site. La structure de ce thème\nstandard est légèrement différente de ce que je vais vous montrer, mais pas tant\nque ça.\nSe plonger dans Hugo : quelques détails techniques\nLaissez-moi commencer en vous disant qu'à un moment donné pendant la migration,\nje ne faisais que modifier des trucs, changer des valeurs, des noms de fichiers,\nla structure, etc. dans l’espoir que les choses allaient marcher comme par magie\net, quand ce n'était pas le cas, je me disais alors : \"Je n'ai aucune idée de\ncomment ou pourquoi ce truc marche\". Et comme l’a dit quelqu'un sur Twitter,\napparemment je ne suis pas la seule à avoir subi ce genre de choses avec Hugo.\nJ'espère donc que cet (assez long) article aidera certains d’entre vous à passer\nà Hugo, et vous évitera au passage quelques maux de têtes.\nAvertissement : Il y a encore beaucoup de choses que je ne sais pas\nencore faire et où je me retrouve parfois à devoir chercher sur Internet. Mais\nj'ai acquis toutes les connaissances de base et de tout ce dont j'ai besoin\npour le moment pour avoir un système fonctionnel, et oui, je sais comment et\npourquoi tout ce qui marche maintenant marche de cette manière. Donc laissez-moi\nvous dévoiler tout ça. Je vous partagerai aussi les articles super utiles que\nj'ai trouvé et qui m'ont également bien aidé. Prenez cet article comme un\npense-bête, un ensemble de rappels, une note à mon futur moi à laquelle je\ndevrai revenir si jamais j'ai besoin de revoir les bases.\nNotez bien que vous finirez sûrement par ne pas utiliser le\nmême processus ou la même arborescence de fichiers que moi. Il est en effet peu\nprobable que vous ayez exactement les mêmes types de contenus que moi. Il se\npeut aussi que vous trouviez une meilleure façon de faire que celle que\nj'utilise actuellement, et c'est tant mieux. Et si vous êtes déjà un pro de Hugo\net que vous repérez des choses qui pourraient être réalisées d’une meilleure\nfaçon, ne vous gênez pas pour partager vos manières de faire avec le reste\nd’entre nous pour que nous puissions tous apprendre de vous.\nLa structure des dossiers d’Hugo\nLa structure du répertoire de mon site en local ressemble actuellement à ça :\n\n\n\n\n\n\nStructure de dossiers pour Hugo.\n\nLes dossiers que vous pouvez voir sur l’image ci-dessus, à l’exception du\ndossier node_modules sont ceux générés pour vous par Hugo lorsque vous\nimportez votre site depuis Jekyll, ce sont ceux que vous devriez normalement\ncréer pour un site géré avec Hugo.\nLes fichiers du bas sont ceux qui sont nécessaires et utilisés par Git et Gulp.\nLe seul fichier qui est utilisé par Hugo est le fichier config.toml.\nconfig.toml contient la configuration de variables du site comme baseURL\nparmi beaucoup d’autres variables que vous allez décider d’utiliser ou pas. Ce\nfichier est similaire au fichier de configuration YAML de Jekyll. La\ndocumentation d’Hugo liste\ntoutes les variables disponibles et\nce que vous devez savoir pour pouvoir utiliser celles dont vous avez besoin. Mon\nfichier de configuration ne contient pas beaucoup de variables pour le moment.\nVotre site est compilé dans le répertoire \/public\/. Il correspond au dossier\ndist qu'on retrouve dans beaucoup d’arborescences d’applications. C’est dans\ntous les autres dossiers que va se dérouler le développement.\nLe dossier static est destiné à héberger les contenus statiques comme les\nimages, les fichiers CSS et JS mais aussi les fichiers audio, vidéo, les slides\nde présentations, etc. Je passe pas mal de temps à travailler dans ce dossier.\nAprès être intervenue sur le redesign de Smashing Magazine,\nj'ai appris que votre structure peut-être différente de celle présentée plus\nhaut. C’est à peu près la même chose mais si vous utilisez un modèle comme\nVictor Hugo de Netlify, votre configuration sera légèrement différente, mais\nc'est du pareil au même pour ce qui est compilé et vers où. Notez que l’adoption\ndu modèle Victor Hugo est un bon moyen de commencer à intégrer Webpack et Gulp\ndans votre workflow. En ce qui me concerne je n'ai pas vraiment besoin de\nWebpack sur mon site vu le peu de JS que j'utilise, mais si vous en avez\nl’utilité, je vous recommande d’utiliser leur template pour Hugo. Et perso, je\npréfère commencer de zéro pour apprendre et comprendre comment tout ça marche.\nFaites comme bon vous semble.\nCréer et mettre en page du contenu\nPour chaque type de contenu dont vous avez besoin, que ce soit une page, un\nbillet de blog, un index de vos articles, de vos études de cas, etc. vous allez\ndevoir créer un fichier Markdown (.md) dans le dossier \/content\/. C’est là\noù sont stockés tous les contenus. Après avoir créé le contenu dans son\nrépertoire spécifique, vous allez créer ou réutiliser un modèle de mise en page\nstocké dans le dossier \/layouts\/.\nChaque fichier .md du dossier \/content\/ correspond à une page qui commence\navec une entête front matter, écrite au format yaml ou toml.\nPuisque je voulais m'imprégner d’un nouvel environnement et que la plupart de la\ndocumentation et des ressources dédiées à Hugo utilisent le format toml, c'est\nle format que j'ai utilisé. Jekyll utilise yaml.2\nJe ne rentrerai pas ici sur les différences entre les deux\nformats, la documentation d’Hugo et Google sont vos amis. Personnellement ça m'a\npris un peu de temps pour apprendre à utiliser toutes ces nouvelles syntaxes\n(TOML, les modèles de template en Go, etc.) avant de me sentir à l’aise.\nNéanmoins la courbe d’apprentissage est assez rapide, ne vous laissez donc pas\nintimider par ces nouvelles syntaxes si tout cela est nouveau pour vous.\nDéfinir (ou déclarer) les types de contenu\nLe front matter de chaque page définit le type de page ou de\ncontenu qui à son tour définit le type de modèle qui sera utilisé pour le rendu.\nLe type de page est défini par la variable type. Par exemple le front matter\nd’un article dans la section blog de mon site ressemble à ça:\n+++\ntype = \"blog\"\ndescription = \"…\"\ntitle = \"…\"\ndate = …\n…\n+++\nLa valeur type peut prendre pratiquement n'importe quelle\nvaleur, et c'est là où on peut se rendre compte du pouvoir\nd’Hugo. Vous pouvez définir autant de types de contenus que vous voulez. Par\nexemple, j'utilise actuellement cinq types de contenus pour mon site :\nstatique (pour les pages comme \"À propos\" et \"Travailler avec moi\"), blog\n(pour les articles comme celui que vous êtes en train de lire), ateliers,\nétudes de cas et bureau (un nouveau type d’articles à paraître bientôt). Je\npeux créer autant de types de contenu que je veux.\nIl est possible de créer des sous-sections de contenu\ndepuis la version 0.24 d’Hugo ! Cela vous permet par exemple de créer des\nsous-sections design et développement dans la section articles et bien\nplus. C’est une fonctionnalité intéressante.\nC’est une des choses que j'aime chez Hugo comparativement à Jekyll qui, à ma\nconnaissance, n'offre pas de fonctionnalité similaire.3\nLa capture d’écran ci-contre montre à quoi ressemble mon dossier \/content\/ en\nce moment :\n\n\n\n\n\n\nLe contenu du dossier content de mon site.\n\nLes pages statiques sont créées dans des fichiers individuels au format Markdown\nà la racine du dossier \/content\/. Les autres types de contenus qui auraient\nbesoin d’un index (comme des articles, des ateliers, des études de cas, etc.)\nsont créés dans des dossiers nommés d’après le type de contenu. Par exemple on\nstockera les contenus de type ateliers dans un dossier \/content\/ateliers\/.\nMes articles se trouvent dans le répertoire \/content\/blog\/. {{&lt; marker &gt;}}Les\ndossiers de ce type sont également appelés des sections.{{&lt; \/marker &gt;}}\nPour chaque contenu, il vous faut définir son type. Vous pouvez faire ça de deux\nmanières.\nLe type pour les pages statiques est défini à l’aide de la variable type dans\nl’entête front matter de la page. Le type des sections (blog,\nateliers, études de cas et bureau) est quant à lui défini à l’aide de\nl’arborescence de dossiers. Vous n'avez pas besoin de spécifier le type dans le\nfront matter lorsque vous vous reposez sur l’arborescence de fichiers. Par\nexemple un billet de blog qui se trouve dans le dossier \/content\/blog\/ sera\nautomatiquement traité comme un type de contenu blog. Inutile de le préciser\ndans le front matter de chaque article.\nVous pouvez choisir de définir le type de contenu à l’aide du front matter ou de\nl’arborescence de fichier. Généralement vous utiliserez la variable type pour\nles pages statiques et vous vous reposerez sur l’arborescence de fichiers pour\nles contenus qui auront besoin d’un index, par exemple des billets de blog.\nUne chose importante à savoir est que {{&lt; marker &gt;}}si vous définissez le type\nde page à l’aide de la variable type, la page peut se trouver n'importe où\ndans le dossier \/content\/, l’arborescence n'aura alors aucune importance.{{%\n\/marker %}}\nVous pourriez donc attribuer le type static à une page et la placer dans le\ndossier blog et Hugo la considérera comme une page statique et ne tiendra pas\ncompte de sa place dans l’arborescence.\nMais… pourquoi donc ? Réponse : pour choisir le type de modèle à utiliser.\nVoyez-vous, chaque type de contenu est associé avec un certain type de mise en\npage. Vous pouvez également utiliser un même modèle pour plusieurs types de\ncontenu. Nous verrons cela dans la partie suivante. Mais d’abord, créons\nquelques pages de contenus : deux pages statiques (Accueil et À propos par\nexemple) et une page d’index pour les articles de blog.\nAvant de faire cela, j'aimerais préciser quelque chose quant à la création de\npages d’index pour différentes sections ou types de contenu.\nLa section blog nécessite la présence d’un fichier _index.md dans le dossier\n\/content\/blog\/. C’est le fichier d’index pour cette section (celui grâce\nauquel nous afficherons la liste de tous les articles). Le dossier\n\/content\/blog\/ hébergera également tous les billets de blog. La capture\nd’écran suivante montre cela de façon plus visuelle :\n\n\n\n\n\n\nLe contenu du dossier \/content\/blog\/.\n\nChaque type de contenu qui utilise cette arborescence de dossiers (ou chaque\nsection de contenu) comporte une page d’index qui commence par un tiret bas\n(_) en plus des fichiers de cette section. De la même manière, tout autre type\nde contenu (ou section) comportera aussi un index et des fichiers pour cette\nsection.\nOK, créons maintenant quelques pages.\nLa page d’accueil\nLa page d’accueil se crée en plaçant un fichier nommé _index.md dans le\ndossier \/content\/ comme vous pouvez le voir dans la capture d’écran un peu\nplus haut.\nLa page d’accueil est un peu spéciale, c'est la seule de toutes les autres pages\nqui nécessite d’avoir son propre modèle de mise en page dans le dossier\n\/layouts\/ – nous parlerons de ces modèles plus en détail dans la prochaine\nsection – et ce modèle de mise en page se nomme aussi index.html.\nVous définissez le type page dans le front matter du fichier\n\/content\/_index.md et vous lui attribuez un titre ainsi qu'une description.\nLe front matter de ma page d’accueil ressemble à ça :\n+++\ntype = \"page\"\ntitle = \"Accueil\"\ndescription = \"Sara Soueidan — Développeuse Web Front-end, auteure et conférencière\"\n+++\nLa description est utilisée dans le fichier partiel d’entête du site en tant que\nvaleur de l’attribut &lt;title&gt; ainsi :\n&lt;title&gt; {{ .Page.Description }} &lt;\/title&gt;\nLa raison pour laquelle je n'utilise pas la valeur du title dans le front\nmatter pour la balise HTML &lt;title&gt; est que dans les autres pages, le title\nde la page est aussi utilisé comme intitulé de lien dans le menu principal de\nnavigation. Mais nous verrons tout ça plus tard.\nLes fichiers Markdown (.md) peuvent contenir du Markdown et du HTML et, comme\npour la page d’accueil, je n'ai aucune entrée dynamique (comme une liste\nd’articles), elle contient juste le code HTML de la page. Mais comment ce code\nMarkdown et HTML sont-ils mis en forme ? Et comment fait-on pour inclure un\nentête et un pied de page ? Tout cela se passe dans le modèle de mise en page.\nLe fichier \/layouts\/index.html est la mise en page utilisée pour l’accueil et\nvoici à quoi il ressemble :\n{{ partial \"homepage-header.html\" . }}\n\n{{ .Content }}\n\n{{ partial \"footer.html\" . }}\n{{ .Content }} récupère le contenu de la page correspondante\ndans le dossier \/content\/. Donc ici ça récupère le contenu de\nla page d’accueil à partir du fichier \/contents\/_index.md.\nEn outre, j'appelle l’entête ainsi que le pied de page à l’aide de fichiers\npartiels.\nPar défaut, quand vous demandez partial \"footer.html .\", Hugo va\nregarder s'il existe un fichier partiel dans le dossier partials situé dans le\nrépertoire layouts.\nReportez-vous à\nla documentation d’Hugo sur les fichiers partiels\npour savoir ce que veut dire le point à la fin, ce qu'il fait et comment on peut\npersonnaliser les appels à des fichiers partiels.\nEt voilà comment on crée une page d’accueil pour son site : un fichier\n\/content\/_index.md qui contient le contenu de la page d’accueil, lui-même mis\nen page à l’aide du fichier \/layouts\/index.html.\nAjouter une page statique\nUne fois la page d’accueil terminée, j'ai voulu m'occuper du reste des pages\nstatiques avant de passer à des contenus plus dynamiques. Je me suis donc mise à\nbâtir la page À propos.\nJ'ai dû faire pas mal de recherches et lire quelques fils de discussions d’aide\nsur le forum d’Hugo et ailleurs pour y parvenir. J'espère donc que ce billet\nvous sera bénéfique si vous cherchez à créer des pages statiques, ce qui s'avère\nétonnement simple en fait.\nLes pages statiques sont créées à la racine du répertoire \/content\/, tout\ncomme la page d’accueil. Toutefois, contrairement à la page d’accueil, les noms\nde fichiers ne commencent pas par un tiret bas.\nEt contrairement à la page d’accueil, vous allez devoir définir le type de page\net dire à Hugo de l’inclure dans le menu principal du site, en lui attribuant un\ntitre et une description.\nPour la page À propos de mon site, j’ai créé un fichier \/content\/about.md.\nLe front matter de la page est le suivant :\n+++\ntype = \"static\"\npage = \"static\/single.html\"\ntitle = \"À propos\"\ndescription = \"À propos de Sara Soueidan — Développeuse Web front-end, auteure et conférencière\"\nmenu = \"main\"\nweight = \"1\"\n+++\nNotez la valeur de type. Comme dit plus haut, vous pouvez attribuer ici la\nvaleur de votre choix. J'ai choisi static, car ça décrit littéralement le type\nde la page. Et aussi parce qu'on trouve beaucoup de ressources en ligne qui\nutilisent ce type pour les pages statiques.\nLa variable page indique à Hugo quel modèle de mise en page présent dans le\nrépertoire \/layouts\/ utiliser.\nIl est bon de noter également que Hugo utilisera automatiquement ce modèle même\nsi je ne lui dis pas. Je me rappelle tout de même avoir eu quelques prises de\ntête au début quand j'essayais de comprendre comment utiliser les modèles pour\nles différentes pages. Je ne savais pas quel modèle allait être utilisé. Même en\nayant lu la documentation, je me suis retrouvée à faire et défaire pas mal de\nchoses pour m'apercevoir que les choses marchaient comme par magie, ou pas du\ntout. Au début, Hugo ressemblait à une boîte noire pour moi et il m'a fallu\nquelques jours pour en comprendre assez et pour oser écrire à son sujet. Quand\nça a fini par fonctionner, j'ai décidé de ne plus toucher au front matter, car\nj'avais peur de casser une fois de plus ma mise en page. Mais maintenant que\nj'en sais davantage, il est bon de signaler que vous n'avez pas vraiment besoin\nde la variable page ici.\nLe title est utilisé comme intitulé de lien dans le menu. (Sur mon site le\nmenu situé en haut de page contient une entrée \"About &amp; Interviews\").\nJe vous ai déjà dit que la description est utilisée dans le fichier partiel\nqui gère l’entête de page, cette description apparait ensuite dans l’onglet de\nvotre navigateur.\nLa variable menu indique à Hugo que cette page doit avoir une\nentrée dans le menu principal.\nLa variable weight est très utile pour vous aider à définir\nl’ordre d’affichage des liens dans le menu. Si vous ne l’utilisez\npas, Hugo utilisera son propre ordre par défaut – qui n'était pas celui que je\nsouhaitais pour mon site. Vous pouvez également définir des valeurs négatives\npour cette variable.\nPour faire court, je vous renvoie une fois de plus à la\ndocumentation d’Hugo pour ce qui est de l’utilisation et de la configuration du\nmenu principal. J'ajoute que certains aspects sont encore assez confus pour moi,\nmais comme je suis arrivée à faire ce que je voulais maintenant : je ne touche\nplus à rien, j'ai trop peur de casser un truc. Une fois de plus. 😂\nToutes les autres pages statiques sont créées de la même manière. La seule chose\nqui change c'est le titre, la description et leur ordre dans le menu. Elles\nutilisent toutes le même modèle de mise en page.\nJe me note quelque chose ici pour plus tard :\nHugo respecte un ordre spécifique pour décider du modèle de mise en page à\nutiliser pour chaque page créée dans le dossier \/content\/. Nous en reparlerons\ndans la section dédiée aux modèles juste après. Donc si nous n'avions pas défini\nle fichier \/layouts\/static\/single.html comme étant le modèle à utiliser, Hugo\naurait utilisé un modèle par défaut stocké dans \/layouts\/. Nous y\nreviendrons.\nEnfin, comme pour la page d’accueil, le contenu HTML de la page À propos se\ntrouve dans le fichier about.md puis il est ensuite inséré dans le modèle\n\/layouts\/static\/single.html à l’aide de {{ .Content }}. Nous faisons aussi\nappel aux fichiers partiels d’entête et de bas de page. Notez la correspondance\nentre le type static et le dossier static situé dans layouts qui contient\nle modèle de mise en page.\nVous n'avez pas à écrire tout le HTML dans le fichier\nMarkdown. Vous pouvez mettre toute la structure du HTML, comme les conteneurs,\netc. dans le modèle de mise en page et n'avoir que le texte dans le fichier\nMarkdown. Si j'ai procédé de la sorte, c'est juste que ça me convient bien comme\nça.\nLes archétypes de contenu\nVous avez peut-être remarqué sur la capture d’écran plus haut que j'ai aussi un\ndossier nommé \/archetypes\/ à la racine de mon site. Ce dossier est lui aussi\nlié aux types de contenu que vous créez. Mais il a un but bien précis.\nPour expliquer à quoi sert ce répertoire, je vais commencer par citer\nla page correspondante de la documentation d’Hugo :\n\nLes archétypes vous permettent de créer de nouvelles instances de types de\ncontenu et de définir des paramètres par défaut à partir de la ligne de\ncommande.\nLes archétypes sont des fichiers de contenu stockés dans le répertoire\narchetypes de votre projet, qui contiennent un front matter pré-configuré\npour les types de contenu de votre site web. Les archétypes facilitent la\nconsistance des métadonnées des contenus à travers tout votre site et\npermettent aux auteurs de générer rapidement de nouvelles instances de type de\ncontenu à l’aide de la commande hugo new\nHugo est capable de déduire l’archétype approprié à l’aide de la section de\ncontenu passée en argument de la commande new :\nhugo new &lt;section-de-contenu&gt;\/&lt;nom-de-fichier.md&gt;\n\nEn d’autres mots, définir un archétype vous permet de créer de nouveaux contenus\nplus rapidement, puisqu'il va remplir le front matter de notre nouvelle page\navec les variables de votre choix.\nPar exemple, supposons que je veuille créer une nouvelle étude de cas (qui irait\ndans \/content\/etudes-de-cas\/). Au lieu de créer un nouveau fichier Markdown\ndans le répertoire, je peux taper cette commande dans le terminal et Hugo va\ncréer le nouveau fichier pour moi :\nhugo new etudes-de-cas\/ma-nouvelle-etude-de-cas.md\nEt les variables de cette nouvelle étude de cas (ma-nouvelle-etude-de-cas.md)\nseront automatiquement ajoutées : nom du client, logo du client (chemin vers\nl’image), description du client, description du projet, date du projet, etc. Par\ndéfaut les valeurs de ces variables seront vierges, prêtes à être renseignées.\nLa capture d’écran suivante montre les variables front matter que j'ai définis\npour l’archétype etudes-de-cas :\n\n\n\n\n\n\nLes 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.\n\nNotez aussi que les autres archétypes que j'ai définis dans le répertoire\narchetypes qui correspondent aux quatre autres types de section qui figurent\nsur mon site. C’est à peu près tout ce qu'il faut savoir sur les archétypes. Si\nvous souhaitez en savoir plus, reportez-vous à la page dédiée dans la\ndocumentation d’Hugo. C’est bien expliqué. Vous n'êtes pas obligés de définir\ndes archétypes, mais je pense que vous en aurez envie.\nPrésenter le contenu avec les modèles de page et créer une page d’index pour les billets\nC’est la partie avec laquelle j'ai eu le plus de mal au début. Comment est-ce\nque je sais que tel modèle est utilisé pour telle section ? Comment est-ce que\nje sais de combien de modèles j'ai besoin ? Et est-ce qu'il y a vraiment besoin\nde modèle ?\nJ'ai pas mal trifouillé et cherché sur le net, puis j'ai passé le plus clair de\nmon temps à faire des essais, jusqu'à avoir des modèles qui fonctionnent bien.\nPuis j'ai tout cassé et refait les choses pour comprendre quand et comment ça\nfonctionnait. Je peux maintenant affirmer avec assurance que j'ai bien compris\ntout ça.\nEn général, pour un blog très simple, vous n'aurez besoin que de deux modèles\npar défaut : list.html et single.html.\nLe modèle list.html aura pour mission d’afficher des listes d’éléments, comme\nsur la page d’index où sont affichées la liste de vos billets de blog.\nQuant au modèle single.html, comme vous l’aurez deviné, il servira pour mettre\nen forme les pages uniques comme celle d’un billet de blog.\nCes deux modèles doivent se trouver dans le répertoire \/layouts\/_defaults\/.\nAinsi, si vous créez un blog avec quelques articles et ne donnez aucune\ninstruction particulière à Hugo à propos de leur mise en page, il ira voir dans\nle dossier \/layouts\/_defaults\/ quels modèles utiliser.\nJ'ai mis en place ces modèles comme solution par défaut sur mon blog, mais je\nles surcharge.\nVous pouvez surcharger les modèles par défaut en fournissant des modèles qui\nporteront le même nom que votre section ou votre type de contenu.\nEn d’autres termes, vous pouvez créer dans le répertoire \/layouts\/ une\nstructure de dossiers similaire à celle que vous avez dans le répertoire\n\/content\/ et Hugo se basera sur cette structure pour déterminer le modèle à\nutiliser.\nOu alors vous pouvez créer un répertoire du même nom que le type que vous avez\ndéfini, comme static par exemple que j'utilise pour les pages statiques.\nPlutôt que d’utiliser le modèle par défaut, Hugo utilisera alors le modèle situé\ndans le répertoire \/layouts\/static\/ pour toutes les pages qui auront le\ntype = static.\nJ'ai pour ma part créé le fichier \/layouts\/static\/single.html que Hugo va\nutiliser pour surcharger la mise en page des pages statiques\n\/layouts\/_default\/single.html .\nEncore une fois la page \/layouts\/static\/single.html est simplement un modèle\navec le contenu suivant :\n{{ partial \"header.html\" . }}\n\n{{ .Content }}\n\n{{ partial \"footer.html\" . }}\ndans lequel le contenu est récupéré à partir des fichiers Markdown respectifs.\nDonc la page about.html est générée à l’aide du modèle de page\n\/layouts\/static\/single.html et {{ .Content }} est remplacé par le contenu du\nfichier \/content\/about.md.\nMaintenant pour créer une page d’index pour une liste d’éléments, comme la page\nde blog et les articles listés ou la page d’ateliers et les pages de détails des\nateliers, on procède de manière très similaire.\nDe la même manière que nous avons créé un répertoire pour le type de contenu qui\nporte le même nom que le type lui-même, nous créons un répertoire pour chaque\nautre type de contenu que nous avons défini à l’aide de notre arborescence de\ndossiers et nous donnons à ce répertoire le même nom que celui du dossier\nprésent dans le dossier content.\nOu si vous préférez : de la même manière que nous avons créé un dossier dans le\nrépertoire layouts\/ du même nom que le type de contenu, nous créons un\ndossier pour chaque section de contenu (blog, ateliers, etudes-de-cas,\netc.) de manière à obtenir une structure de dossiers similaire dans layouts à\ncelle que nous avons dans \/content\/.\nC’est toujours pas clair ? Alors regardez ce que ça donne pour mon site :\n\n\n\n\n\n\nLa structuration des répertoires pour le contenu et les modèles de mon site.\n\nAttardons-nous à nouveau quelques instants sur la section blog. Au répertoire\n\/content\/blog\/ correspond le répertoire \/layouts\/blog\/.\nÀ l’intérieur du répertoire \/content\/blog\/ se trouve la page d’index\n_index.md et les articles de blog.\nDans \/layouts\/blog\/ nous avons le modèle list.html ainsi que celui de la\npage single.html.\nHugo utilisera le modèle list.html pour la page _index.md et le modèle\nsingle.html pour chacun des articles de blog.\nDe la même manière, toutes les autres sections possèdent leur propre répertoire\nde modèles, qui contient les modèles list.html et single.html.\nEncore une fois vous n'avez pas réellement besoin de tous ces modèles. Et vous\naurez peut-être remarqué que quelques-unes des pages sont en tout point\nsimilaires à l’exception de leur nom. Si je fais ça, c'est uniquement pour des\nraisons de flexibilité future. Si jamais je veux changer le modèle de l’un des\ntypes de section, j'aurai simplement à modifier son modèle correspondant. Si\nvotre site est plus simple et n'utilise pas autant de types de contenus, vous\nn'avez surement pas besoin de créer autant de modèles que moi.\nLa seule exception à la structuration des répertoires de modèles c'est la page\nd’accueil, dont le modèle de mise en page est placé à la racine du répertoire\n\/layouts\/ et se nomme index.html.\nIl est important de vérifier l’ordre dans lequel Hugo va choisir le modèle à\nutiliser pour chaque page. Je vous le recommande vivement.\nPour citer la documentation :\n\nHugo obéit à plusieurs règles pour savoir quel modèle utiliser pour effectuer\nle rendu d’une page spécifique. Hugo va utiliser la liste priorisée suivante.\nSi un fichier n'est pas présent, alors on utilisera le suivant dans la liste.\nCela vous permet de concevoir des modèles particuliers quand vous le souhaitez\nsans devoir créer plus de modèles que nécessaire. Pour la plupart des sites,\nseul le fichier _default en fin de liste sera nécessaire. Les utilisateurs\npeuvent spécifier le type et le modèle dans le front matter. La section est\ndéterminée en fonction de l’endroit où se trouve le fichier de contenu. Si le\ntype est fourni, il sera utilisé à la place de la section.\n\nVous en apprendrez davantage sur cet ordre de priorisation dans\nla page qui documente l’organisation des contenus.\nBoucler sur les listes de section\nLe dernier point technique sur Hugo que je veux aborder concerne le listing des\narticles d’une section sur la page d’index de cette section.\nUne fois de plus, basons-nous sur l’exemple de la section blog située dans\n\/content\/blog\/.\nLes fichiers Markdown ne contiennent bien entendu aucune logique de modèle. Donc\npour lister tous les billets de blog, nous allons devoir faire cela dans le\nmodèle correspondant à cette page d’index, situé dans \/layouts\/blog\/list.html.\nLa boucle et toute la logique de modèle est écrite à l’aide du\ntemplating HTML du langage Go.\nLa boucle en elle-même pourra et sera probablement différente pour la majorité\nd’entre vous. Après avoir pas mal cherché, je suis arrivée à écrire la boucle\nsuivante qui affiche les cinq derniers articles, suivi d’un appel à un fichier\npartiel pour la gestion de la pagination.\n&lt;ul class=\"articles-list\"&gt;\n    &lt;!-- Boucle à travers les fichiers situés dans content\/blog\/*.md --&gt;\n    {{ range (.Paginator 5).Pages }}\n    &lt;li class=\"post\"&gt;\n        &lt;a class=\"post-title\" href=\"{{.RelPermalink}}\"&gt;{{ .Title }}&lt;\/a&gt;\n        &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;\n\n        &lt;div class=\"post-summary\"&gt;\n            {{ .Summary }} &lt;!-- extrait automatiquement le premier paragraphe du fichier Markdown de l’article --&gt;\n        &lt;\/div&gt;\n\n        &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;\n    &lt;\/li&gt;\n\n    {{ end }}\n&lt;\/ul&gt;\n\n{{ partial \"pagination.html\" . }}\nNe faites pas attention au code HTML de cette boucle, ça fait\nun moment que je n'ai pas travaillé sur mon site, il aurait bien besoin de\nquelques améliorations. Le balisage sera bientôt mis à jour.\nC’est la partie {{ range .Paginator.Pages }} qui est vraiment importante ici.\n{{&lt; marker &gt;}}Chaque .Paginator que vous utilisez dans une page d’index de\nsection va boucler et afficher les articles de cette section.{{&lt; \/marker &gt;}}\n(.Paginator 5).Pages indique à Hugo de ne lister que cinq éléments. Cette\nboucle va parcourir tous les articles de la section blog et ne lister que les\ncinq plus récents. Une boucle similaire dans le fichier\nlayouts\/workshops\/index.html bouclerait sur les ateliers stockés dans le\ndossier \/content\/workshops\/ et afficherait la liste des ateliers dans l’index.\nJe confonds encore quelques variables globales du site et des\nvariables de page dans Hugo. Ce que j'ai pour le moment me suffit, et si jamais\nj'avais besoin de plus de flexibilité, d’options ou de fonctionnalités, il\nfaudra que je me replonge de nouveau dans la documentation pour arriver à tirer\nde la logique d’Hugo plus qu'une simple boucle. Vous devriez en faire de\nmême.\nEt pour ce qui est du fichier partiel pagination.html, le mien ressemble pour\nle moment à ça :\n{{ $baseurl := .Site.BaseURL }}\n{{ $pag := .Paginator }}\n\n{{ if gt $pag.TotalPages 1 }}\n\n&lt;nav class=\"center pagination\"&gt;\n    {{ range $pag.Pagers }}{{ if eq . $pag }}&lt;span class=\"pagination__button button--disabled\"&gt;{{ .PageNumber }}&lt;\/span&gt;{{ else }}&lt;a class=\"pagination__button\" href='{{ $baseurl }}{{ .URL }}'&gt;{{ .PageNumber }}&lt;\/a&gt;{{ end }}{{ end }}\n\n    &lt;div class=\"clearfix\"&gt;\n        {{ if .Paginator.HasPrev }}\n        &lt;a class=\"pagination__button pagination__button--previous\" title=\"Page précédente\" href=\"{{ .Paginator.Prev.URL }}\"&gt;\n            Articles plus récents\n        &lt;\/a&gt;\n        {{ else }}\n        &lt;span class=\"pagination__button pagination__button--previous button--disabled\"&gt;Articles plus récents&lt;\/span&gt;\n        {{ end }}\n\n        {{ if .Paginator.HasNext }}\n        &lt;a class=\"pagination__button pagination__button--next\" title=\"Next Page\" href=\"{{ .Paginator.Next.URL }}\"&gt;\n            Articles plus anciens\n        &lt;\/a&gt;\n        {{ else }}\n        &lt;span class=\"pagination__button pagination__button--next button--disabled\"&gt;Articles plus anciens&lt;\/span&gt;\n        {{ end }}\n    &lt;\/div&gt;\n\n    &lt;a href=\"..\/article-archives\/\" class=\"button button--full\"&gt;Voir la liste de tous les articles&lt;\/a&gt;\n\n&lt;\/nav&gt;\n\n{{ end }}\nLibre à vous d’aller en apprendre plus sur les variables. Je trouve que le code\nci-dessus est compréhensible tel quel, mais encore une fois, si vous avez besoin\nde plus de fonctionnalités, la documentation et le forum vous seront\nprobablement d’une plus grande aide.\nCréer une page d’archive\nEn plus de la page de blog par défaut, je voulais ajouter une page d’archive qui\nliste la totalité de mes articles sur une seule et unique page. Ce n'était pas\naussi évident que je l’aurais cru. La documentation ne m'a pas beaucoup aidée.\nEt j'ai dû à nouveau faire des recherches. Je suis tombée sur\ncet article extrêmement utile\net j'ai eu recours à la même technique que celle exposée par l’auteur.\nPour la page d’archive, j'ai créé une page statique dans \/content\/ et je lui\nai donné un nouveau type: archive. La page utilise le modèle situé dans\n\/layouts\/archive\/single.html.\nDans le modèle de page, je boucle sur les articles comme pour la page d’index du\nblog, mais avec une différence importante :\n&lt;!-- \/layouts.archive\/single.html --&gt;\n\n{{ range where .Site.Pages \"Type\" \"blog\" }}\n&lt;li class=\"post\"&gt;\n    &lt;a class=\"post-title\" href=\"{{.RelPermalink}}\"&gt;{{ .Title }}&lt;\/a&gt;\n    &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;\n\n    &lt;div class=\"post-summary\"&gt;\n        {{ .Summary }} &lt;!-- récupère automatiquement le premier paragraphe de l’article --&gt;\n    &lt;\/div&gt;\n\n    &lt;p&gt;&lt;small&gt;&lt;a href=\"{{.RelPermalink}}\" class=\"read-more-link\"&gt;Lire la suite ››&lt;\/a&gt;&lt;\/small&gt;&lt;\/p&gt;\n&lt;\/li&gt;\n{{ end }}\nEn résumé : .Site.Pages boucle sur toutes les pages de votre\nsite. En d’autres termes, cela va lister tous les fichiers Markdown contenus\ndans le dossier \/content\/. Pour indiquer à Hugo de n'afficher\nque les fichiers situés dans la section \/content\/blog\/, on “filtre” les pages\nen précisant le \"Type\" \"blog\". On procédera également de la sorte pour une\npage d’archive d’une autre section, en utilisant le nom de la section comme\nfiltre. Et c'est tout.\nHéberger chez Netlify\nJ'avais choisi d’héberger mon site avec GitHub Pages depuis quelques années.\nPuis est arrivé un moment où ça commençait à faire un peu juste. Il semble qu'il\ny ait eu aussi régulièrement de curieux problèmes de cache et je devais pousser\ndeux fois les changements sur le dépôt pour que ces derniers soient pris en\ncompte (j'imagine que le cache n'était pas invalidé quand il devait l’être).\nJ'ai donc commencé à devoir créer des enregistrements vides juste pour vider le\ncache et être capable de voir les changements que j'avais faits en production.\nMaintenant, je ne suis pas certaine que c'était vraiment un problème de cache,\nbien que ça y ressemblait beaucoup. Je ne sais pas non plus si quelqu'un d’autre\nest capable de reproduire ce problème. Et non, je n'ai pas contacté le support\nde GitHub à ce sujet. Je détestais tellement mon site Web que je me suis dit\n\"j'ai déjà assez bien de problèmes en local pour me soucier de ce problème en\nproduction\", j'en ai donc fait totalement abstraction.\nJ'ai pu aussi me rendre compte de l’ultra-rapidité de\nNetlify quand j'ai travaillé sur Smashing Magazine.\nDe plus, Netlify permet de \"rendre votre site ou votre application web bien plus\nrapide en la servant au plus près des utilisateurs. Au lieu d’un serveur unique,\nvous déployez sur un réseau global de nœuds CDN intelligents, qui gère aussi\nl’unicité des assets, la mise en cache automatique des entêtes, les redirections\net les réécritures intelligentes.\"\nEt en plus de tout ça, si vous êtes un développeur et que vous travaillez en\nopen source, Netlify vous offre un abonnement Pro à vie. Tout ce qu'ils\ndemandent en retour est un lien vers Netlify sur votre site ou votre\napplication. Pour moi ce ne fut pas un problème vu que je mentionne toujours où\nmon site est hébergé dans le bas de page. J'ai donc signé pour la formule Pro.\nUn hébergement gratuit et rapide ! Woohoo !\nLa configuration de votre site se fait en quelques clics :\n\nCréer un compte sur netlify.com\nRelier son compte Netlify à son dépôt de code. Le mien est hébergé sur GitHub,\nj'ai pu le connecter depuis l’interface de Netlify.\nSpécifier le dossier de destination ainsi que la commande de build,\nrespectivement public et hugo dans mon cas. (Voir les captures d’écrans\nci-dessous)\nConfiguration de votre nom de domaine. Cela demande de faire quelques\nchangements de DNS.\nCela m'a demandé seulement 3 clics pour bénéficier d’un certificat SSL\nrenouvelé automatiquement et d’une connexion HTTPS pour mon site.\nEt… c'est tout.\n\nJe devrais probablement mentionner le fait que j'ai rencontré quelques\ndifficultés lorsque j'ai fait la bascule, mais ce n'était pas de la faute de\nNetlify. L'équipe de Netlify a même été super et m'a aidée à déboguer les\nproblèmes que je rencontrais. Après avoir effectué les changements dans la\nconsole du registrar de mon domaine, cela a pris quelques heures pour que mon\nsite soit en ligne avec mon nom de domaine personnalisé.\nQuelques bons trucs à savoir :\n\nAjouter votre dossier \/public\/ à votre fichier .gitignore. Netlify va\nlancer la génération de votre site sur ses serveurs. Pour éviter de possibles\nconflits, ne versionnez pas votre dossier de destination dans votre dépôt. Le\nmien n'est présent que sur ma machine. Je rencontrais des problèmes de rendus\navec certains templates quand je le versionnais auparavant.\nVérifiez bien la version d’Hugo que vous utilisez (hugo version) et celle\nutilisée par Netlify. Au début j'ai eu droit à des erreurs de build qui\nempêchaient le déploiement, car ma version était plus récente que celle de\nNetlify. Si c'est le cas\najoutez une variable d’environnement à votre site\nqui correspond à la version d’Hugo que vous utilisez localement.\n\nVoici en partie à quoi ressemble mon tableau de bord Netlify :\n\n\n\n\n\n\nParamètres de déploiement et variables environnement dans le tableau de bord de Netlify.\n\nJ'aime aussi le fait que Netlify propose des options pour optimiser et assembler\nles assets pour vous, afin d’améliorer les performances globales de votre site.\n\n\n\n\n\n\nOptions d’optimisation des assets dans le tableau de bord de Netlify.\n\nJ'ai constaté quelques améliorations et plus de A verts sur la page de résultats\nsur webpagetest.org alors qu'ils étaient rouges\nauparavant. J'ai encore du travail de ce côté-là.\nRésumé de ma configuration actuelle\n\nLe code source du site web est hébergé sur GitHub,\nJ'utilise Hugo comme générateur de site statique,\nDéploiement automatiquement à chaque push sur le dépôt grâce à Netlify,\nHébergée gratuitement chez Netlify avec le plan Open Source.\n\nIl est également utile de mentionner que désormais la compilation complète de\nmon site après chaque changement, sans avoir à filtrer de vieux contenus, prend\nà Hugo moins de 40 millisecondes. {{&lt; marker &gt;}}Hugo met 39ms à compiler mon\nsite pour être plus précis{{&lt; \/marker &gt;}}, là où Jekyll, même avec des options\ncomme --incremental mettait plusieurs minutes.\nObjectifs futurs\nOn retrouve ici quelques-unes des choses qui figurent sur ma TODO liste depuis\nquelques années et que j'avais jusqu'ici remis à plus tard, en partie à cause de\nla situation dans laquelle je me trouvais précédemment avec Jekyll :\n\nLancer une mailing-list. C’est prévu d’ici la fin du mois.\nUne nouvelle section pour les articles qui ne rentrent pas dans la section des\narticles techniques.\nAméliorer la qualité du code du site pour ne plus être embarrassée et rendre\nle dépôt public sur Github.\nRendre le site disponible en mode offline. Et le rendre encore plus\nrapide.\nIl y aura une FAQ mais pas au format des AMA (Ask Me Anything) qu'on\ntrouve sur GitHub. Il y a des aspects que je n'aime pas dans ce format. Plus\nd’informations et de détails dès que la lettre d’information paraîtra.\nÉcrire plus régulièrement. Je laisse beaucoup trop d’idées de côté que je\ndevrais transformer en articles de blog. Je me suis promise d’écrire plus\nsouvent, même si ces idées d’articles ne sont pas aussi poussées que\nd’habitude. Cet article est un début.\n\nQuelques mots de conclusion ?\nJe laisserai à Agnès le soin d’exprimer ce que je ressens vis-à-vis de cette\nnouvelle configuration, même si je sais que je peux et que j'améliorerai encore\nquelques trucs dans le futur :\n\n\n\nAu moins maintenant je dispose d’un système qui m'évitera des maux de tête à\nchaque changement que je voudrais apporter à mon site Web. Je prends de nouveau\nplaisir à écrire des articles de blog, ce qui veut dire que vous pouvez vous\nattendre à de prochaines publications dans les semaines à venir.\nMerci de m'avoir lue jusqu'ici.\n\n\n\n\nNdT: 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 incremental activée.&#160;&#8617;\n\n\nNdT: Pour la petite histoire c'est Tom Preston-Werner, le créateur de Jekyll qui est à l’origine de TOML (d’où son nom). Vous pouvez apprendre TOML en quelques minutes, même chose pour YAML&#160;&#8617;\n\n\nNdT: C’est inexact, Jekyll offre la possibilité de créer ses propres types de contenu avec les collections.&#160;&#8617;\n\n\n",
      "content_html": "<aside class=\"note note-intro\"><p>Si vous faites du développement front-end, du CSS, du SVG et\nautres joyeusetés, vous connaissez sans doute déjà la talentueuse\n<a href=\"https://www.sarasoueidan.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Sara Soueidan</a>. Il se trouve que Sara a travaillé\nrécemment sur la refonte de Smashing Magazine et <a href=\"/2017/03/17/smashing-mag-va-dix-fois-plus-vite/\">la migration de WordPress à\nHugo</a>. Cette mission lui\na permis de se familiariser avec Hugo et de découvrir au passage le service\noffert par <a href=\"https://www.netlify.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Netlify</a>, la nouvelle référence en termes\nd’hébergement d’applications statiques. Fatiguée des faibles temps de\ncompilation proposés par Jekyll (dus en grande partie à la lenteur de Kramdown,\nle parseur Markdown utilisé, et à ses traitements d’expressions régulières),\nSara en a profité pour s'attaquer à la migration de son site perso. Comme\nbeaucoup d’autres, elle a été immédiatement séduite par les performances\nproposées par Hugo, le générateur statique ultra-rapide et ultra-souple écrit en\nGo. Elle nous livre ici en détails le récit de cette migration qu'elle est bien\ncontente d’avoir menée à bien. Puisse le partage de son périple vous épargner de\nsubir les mêmes écueils et vous aider à commencer à vous familiariser avec les\nconcepts d’Hugo.</p></aside>\n<p>Ces derniers mois, travailler sur mon site web s'est révélé être de plus en plus\npénible, que ce soit pour continuer à le développer, itérer sur son design,\nécrire un article de blog ou mettre à jour mes pages conférences et ateliers.\nC'était dû en partie à <a href=\"https://jekyllrb.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Jekyll</a>, le générateur de site\nstatique que j'utilisais alors. Le vent du changement commençait à souffler.</p>\n<p>Jekyll était devenu incroyablement lent et chaque changement entraîne une\nrecompilation… C'était devenu tellement lent qu'<strong>attendre que la compilation du\nsite soit terminée est devenue une vraie torture, tellement chronophage qu'il\nfallait que je m'en débarrasse à tout prix</strong>.</p>\n<p>On pourrait croire que j'exagère, mais je vous promets que non. Jekyll est\ndevenu beaucoup trop lent. \"Trop lent\" est en réalité un euphémisme.\nDernièrement chaque fois que je modifiais une propriété CSS ou que j'effectuais\nune modification du code HTML <strong>je devais attendre jusqu'à cinq minutes pour que\nle changement soit pris en compte et compilé par Jekyll</strong>. Encore une fois je\nn'exagère <em>pas</em>. Jekyll se figeait littéralement. Il fallait que je l’arrête en\nfaisant CTRL-C pour débloquer la situation et que je le relance pour que les\nchangements soient pris en compte et qu'il finisse par compiler. Et si\nj'effectuais beaucoup de changements d’affilée, le ventilateur de mon Macbook\ncommençait à s'emballer comme un fou, l’ordinateur chauffait et faisait le bruit\nd’un avion sur le point de décoller. <sup id=\"fnref1:1\"><a href=\"#fn:1\" class=\"footnote-ref\">1</a></sup></p>\n<p>Je dirais que mon site est de taille modeste. J'ai moins d’une centaine de\nbillets de blog, même moins de 60 à l’heure où j'écris cet article, et seulement\nquelques pages statiques. Je ne me repose pas beaucoup sur JavaScript. En fait,\nj'ai à peine besoin d’utiliser la moindre ligne de JavaScript. Et pourtant,\nJekyll avait du mal à chaque fois qu'il devait compiler.</p>\n<p>Oui, j'ai utilisé des options comme <code>--incremental</code> et toutes celles que l’on\nm'a recommandées pour accélérer le processus de compilation. Sans le moindre\nrésultat.</p>\n<p>J'ai même du mal à souligner à quel point cela a empiré ces douze derniers mois.\nJe ressentais littéralement une montée d’hormones de stress dans mon flux\nsanguin à chaque fois que j'imaginais que je devais apporter un changement à mon\nsite Web. Je savais que j'allais vivre un enfer en faisant cela.</p>\n<p>Mais je savais que cela ne pourrait durer éternellement. Je savais que je\ndevrais laisser tomber Jekyll et migrer à un moment donné vers un nouveau\ngénérateur. Je n'ai simplement jamais eu le temps de le faire. Pour être\nhonnête, je n'ai jamais vraiment <em>pris</em> le temps de le faire, car à chaque fois\nque j'avais du temps de libre je voulais profiter au maximum de ce temps en\nrestant <em>éloignée</em> de mon ordinateur. Mon site n'était simplement pas une\npriorité, surtout que j'étais encore indécise sur l’alternative à utiliser. Du\ncoup j'ai continué de le laisser en l’état.</p>\n<p>Mais dernièrement, sachant que j'avais quelques semaines de libres pour faire ce\nque je voulais et comme j'ai commencé à avoir plein d’idées pour mon blog et que\nje voulais vraiment qu'elles voient le jour, comme mettre en place une lettre\nd’information, modifier le design, améliorer le code (un travail toujours en\ncours), ajouter un nouveau type de contenu (c'est pour bientôt) et quelques\nautres idées, j'ai finalement réussi à m'y mettre et à le faire, car je\n<em>voulais</em> que mes idées prennent forme et écrire quelques billets de blog.\n<strong>Mais d’abord j'avais besoin de pouvoir de nouveau prendre du plaisir à\ntravailler sur mon site web</strong>. Du coup je me suis dit : <em>\"Ça suffit, je vais\ndevoir m'y mettre pour de bon pendant quelques jours cette semaine et dédier du\ntemps à passer à un nouveau générateur de site statique\"</em>. Je savais que c'était\nun investissement personnel nécessaire et extrêmement utile que je devais me\nrésoudre à faire. Je m'y suis investie et je l’ai fait. (C’est vraiment la\nmanière la plus efficace d’être productif : le faire.)</p>\n<h3 id=\"choisir-un-generateur-de-site-statique\">Choisir un générateur de site statique</h3>\n<p>Comme je l’ai dit plus haut, une des raisons pour laquelle je n'ai pas changé de\ngénérateur plus tôt c'est parce que je ne savais pas lequel je voulais utiliser.\nPlusieurs personnes sur Twitter m'ont gentiment suggéré quelques-unes des\nnombreuses options disponibles. Mais aucune de ces options ne m'allait.\nVoyez-vous, chaque personne a son propre mode de fonctionnement et ses\npréférences lorsqu'il s'agit d’organiser ses fichiers, ses dossiers et son\ntravail. Aucun des générateurs statiques que j'ai regardés n'avait ce que je\nrecherchais pour mon site. Jusqu'à ce que quelqu'un me suggère de jeter un œil à\n<a href=\"https://gohugo.io\" target=\"_blank\" rel=\"noopener noreferrer\">Hugo</a>.</p>\n<p>J'ai passé la documentation en revue quelques minutes, simplement pour me faire\nune idée de ce à quoi je pouvais m'attendre et de ce que Hugo avait à offrir –\nhistoire d’avoir une première impression, à proprement parler. Après avoir lu la\npartie sur la structuration des contenus et leur organisation et appris comment\nHugo offre la possibilité de créer plein de sections et de catégories de\ncontenus différents, en plus de la souplesse générale qu'il procure, je me suis\ndit que c'était le générateur de site statique dont j'avais toujours rêvé et\ncelui dont j'avais besoin. L'organisation et la structure ressemblait exactement\nà ce que j'avais pu imaginer pour mon propre site.</p>\n<p>Mais ce qui m'a fait adopté Hugo plus que toute autre option, c'est de voir\n<a href=\"https://novelist.xyz/tech/hugo-vs-jekyll-static-site-generator/\" target=\"_blank\" rel=\"noopener noreferrer\">à quel point il est rapide</a>\ncomparé à Jekyll. Non seulement chaque billet de blog que j'ai pu lire y est\nallé d’une comparaison qui atteste ce fait, mais j'ai aussi pu faire\nl’expérience de cette vitesse pour la première fois lorsque j'ai travaillé sur\nla <a href=\"/2017/03/17/smashing-mag-va-dix-fois-plus-vite/\">refonte de Smashing Magazine</a>.</p>\n<p>La nouvelle version de Smashing Magazine (actuellement accessible via\n<a href=\"https://next.smashingmagazine.com\" target=\"_blank\" rel=\"noopener noreferrer\">next.smashingmagazine.com</a>) utilise Hugo\ncomme générateur de site statique. La configuration que j'ai utilisée lorsque je\nmontais le front-end du magazine s'est montrée tellement rapide que je n'avais\naucun doute quant à la véracité des résultats que je pouvais lire. Et comme mon\nsite est bien plus petit que Smashing Magazine, je savais que je n'avais aucun\nsouci à me faire. Si Smashing Magazine pouvait être compilé aussi rapidement,\npourquoi pas mon blog ?</p>\n<aside class=\"note note-info\"><p>Veuillez prendre note que cet article n'est en aucun cas\ndestiné à constituer un guide exhaustif sur Hugo. Il me reste encore beaucoup de\nchoses à comprendre, je suis donc mal placée pour écrire un tel guide. Vous\nverrez que vous devrez vous reporter à la documentation d’Hugo pour en savoir\nplus sur les sujets que je vais aborder. Prenez cet article comme un guide qui\npeut vous aider à savoir par où commencer (et parfois savoir quoi faire) sur\ncertaines thématiques particulières propres à Hugo. Et ce n'est en fin de compte\npas une comparaison entre Hugo et Jekyll. C’est davantage une introduction à\nHugo qui comporte quelques astuces. Si vous envisagez d’adopter Hugo comme\nnouveau générateur de site statique, j'espère que vous trouverez quelques trucs\nutiles pour avoir un système fonctionnel.</p></aside>\n<h3 id=\"configurer-hugo\">Configurer Hugo</h3>\n<p>Configurer Hugo n'est pas compliqué. Il y a deux guides dans la documentation :\nun pour\n<a href=\"https://gohugo.io/tutorials/installing-on-mac/\" target=\"_blank\" rel=\"noopener noreferrer\">installer Hugo sur un Mac</a> et\nun pour\n<a href=\"https://gohugo.io/tutorials/installing-on-windows/\" target=\"_blank\" rel=\"noopener noreferrer\">l’installer sur Windows</a>.\nDans cet article je ferai toujours référence à la configuration pour un Mac,\npuisque c'est ma principale machine de travail.</p>\n<p>J'ai utilisé <code>brew</code> pour installer Hugo :</p>\n<pre><code class=\"language-sh hljs bash\">brew install hugo</code></pre>\n<p>J'ai suivi les instructions présentes sur la page d’installation, mis à jour\n<code>brew</code> et lancé quelques commandes pour m'assurer que tout était bien installé\net fonctionnait correctement. C’est tout ce dont vous avez besoin pour qu'Hugo\ntourne sur votre machine. Difficile de faire plus simple. Avec Jekyll, ce\nn'était pas aussi indolore, je me rappelle avoir passé pas mal de temps à le\nconfigurer pour le faire tourner à l’époque.</p>\n<p>J'ai parfois tendance à être une développeuse paresseuse. Mais ça a du bon, car\ncela me pousse à trouver la manière la plus rapide et la plus simple de mener à\nbien une tâche. Et donc la première des choses que j'ai voulu faire a été de\nmigrer automatiquement tous mes articles de blog dans Hugo sans avoir à repasser\nsur chacun des billets pour modifier le <a href=\"https://gohugo.io/content/front-matter/\" target=\"_blank\" rel=\"noopener noreferrer\">front matter</a>. (J'aurais\nvraisemblablement abandonné si j'avais dû faire cela 😅)</p>\n<p>Heureusement, depuis la version 0.15, Hugo offre\n<a href=\"https://gohugo.io/commands/hugo_import_jekyll/\" target=\"_blank\" rel=\"noopener noreferrer\">une commande pour migrer depuis Jekyll</a>.\nVous n'avez qu'à taper la ligne suivante dans le terminal – en remplaçant\n<code>chemin_site_jekyll</code> et <code>repertoire_destination</code> par les chemins vers le\nrépertoire utilisé actuellement pour votre site sous Jekyll et celui dans lequel\nvous voulez configurer votre nouveau site – et Hugo se chargera d’importer les\nfichiers de votre installation actuelle de Jekyll dans le répertoire qui\ncontiendra votre site Hugo :</p>\n<pre><code class=\"language-sh hljs bash\">hugo import jekyll chemin_site_jekyll repertoire_destination</code></pre>\n<p>Si vous n'importez pas un site depuis Jekyll, vous pouvez toujours aller lire la\ndocumentation qui détaille ce qu'il faut savoir sur la structure des répertoires\nde Hugo, où ranger les assets, le contenu, les modèles de mise en page et bien\nplus.</p>\n<p>L'étape suivante consiste à convertir vos modèles Jekyll en modèles Hugo et\nc'est là où réside la plus grande partie du travail et où je me suis arrachée\nles cheveux pas mal de fois. Mais croyez-moi, le résultat final prouve que ça\nvalait <strong>vraiment</strong> le coup. Au passage, j'ai beaucoup appris. C’est ce que je\nvais partager avec vous dans la prochaine section.</p>\n<aside class=\"note note-tip\"><p><strong>Astuce</strong> : Vous appartenez peut-être à une autre catégorie\nde développeur fainéant, vous préférez peut-être partir d’un modèle standard qui\nvous fournit la configuration dont vous avez besoin et qui est prêt pour que\nvous puissiez ajouter du contenu sur le champ, surtout si vous démarrez un\nnouveau blog. Dans ce cas je vous recommande chaudement le\n<a href=\"https://github.com/netlify/victor-hugo\" target=\"_blank\" rel=\"noopener noreferrer\">modèle Victor Hugo</a> de Netlify, qui\ncontient tout ce qu'il faut, il y a même Webpack et Gulp de correctement\nconfigurés pour pouvoir faire tourner votre site. La structure de ce thème\nstandard est légèrement différente de ce que je vais vous montrer, mais pas tant\nque ça.</p></aside>\n<h3 id=\"se-plonger-dans-hugo-quelques-details-techniques\">Se plonger dans Hugo : quelques détails techniques</h3>\n<p>Laissez-moi commencer en vous disant qu'à un moment donné pendant la migration,\nje ne faisais que modifier des trucs, changer des valeurs, des noms de fichiers,\nla structure, etc. dans l’espoir que les choses allaient marcher comme par magie\net, quand ce n'était pas le cas, je me disais alors : \"Je n'ai aucune idée de\ncomment ou pourquoi ce truc marche\". Et comme l’a dit quelqu'un sur Twitter,\napparemment je ne suis pas la seule à avoir subi ce genre de choses avec Hugo.\nJ'espère donc que cet (assez long) article aidera certains d’entre vous à passer\nà Hugo, et vous évitera au passage quelques maux de têtes.</p>\n<p><strong>Avertissement :</strong> Il y a encore beaucoup de choses que je ne sais <strong>pas\nencore</strong> faire et où je me retrouve parfois à devoir chercher sur Internet. Mais\nj'ai acquis toutes les connaissances de base et de tout ce dont j'ai besoin\n<strong>pour le moment</strong> pour avoir un système fonctionnel, et oui, je sais comment et\npourquoi tout ce qui marche maintenant marche de cette manière. Donc laissez-moi\nvous dévoiler tout ça. Je vous partagerai aussi les articles super utiles que\nj'ai trouvé et qui m'ont également bien aidé. Prenez cet article comme un\npense-bête, un ensemble de rappels, une note à mon futur moi à laquelle je\ndevrai revenir si jamais j'ai besoin de revoir les bases.</p>\n<aside class=\"note note-info\"><p>Notez bien que vous finirez sûrement par ne pas utiliser le\nmême processus ou la même arborescence de fichiers que moi. Il est en effet peu\nprobable que vous ayez exactement les mêmes types de contenus que moi. Il se\npeut aussi que vous trouviez une meilleure façon de faire que celle que\nj'utilise actuellement, et c'est tant mieux. Et si vous êtes déjà un pro de Hugo\net que vous repérez des choses qui pourraient être réalisées d’une meilleure\nfaçon, ne vous gênez pas pour partager vos manières de faire avec le reste\nd’entre nous pour que nous puissions tous apprendre de vous.</p></aside>\n<h4 id=\"la-structure-des-dossiers-d-hugo\">La structure des dossiers d’Hugo</h4>\n<p>La structure du répertoire de mon site en local ressemble actuellement à ça :</p>\n<figure>\n<picture title=\"Structure de dossiers pour Hugo.\">\n<source type=\"image/webp\" srcset=\"/d33wubrfki0l68.cloudfront.net/4aa07c8129bdae37f8c6510453f274a32ac664c0/09ca5/images/article-assets/hugo-netlify/hugo-folder-structure.092af9f3ad903086345d112024559dd4.webp\" width=\"494\" height=\"682\">\n<source type=\"image/avif\" srcset=\"/d33wubrfki0l68.cloudfront.net/4aa07c8129bdae37f8c6510453f274a32ac664c0/09ca5/images/article-assets/hugo-netlify/hugo-folder-structure.092af9f3ad903086345d112024559dd4.avif\" width=\"494\" height=\"682\">\n<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;\">\n</picture>\n<figcaption>Structure de dossiers pour Hugo.</figcaption>\n</figure>\n<p>Les dossiers que vous pouvez voir sur l’image ci-dessus, à l’exception du\ndossier <code>node_modules</code> sont ceux générés pour vous par Hugo lorsque vous\nimportez votre site depuis Jekyll, ce sont ceux que vous devriez normalement\ncréer pour un site géré avec Hugo.</p>\n<p>Les fichiers du bas sont ceux qui sont nécessaires et utilisés par Git et Gulp.\nLe seul fichier qui est utilisé par Hugo est le fichier <code>config.toml</code>.</p>\n<p><code>config.toml</code> contient la configuration de variables du site comme <code>baseURL</code>\nparmi beaucoup d’autres variables que vous allez décider d’utiliser ou pas. Ce\nfichier est similaire au fichier de configuration YAML de Jekyll. La\ndocumentation d’Hugo liste\n<a href=\"https://gohugo.io/overview/configuration/\" target=\"_blank\" rel=\"noopener noreferrer\">toutes les variables disponibles</a> et\nce que vous devez savoir pour pouvoir utiliser celles dont vous avez besoin. Mon\nfichier de configuration ne contient pas beaucoup de variables pour le moment.</p>\n<p>Votre site est compilé dans le répertoire <code>/public/</code>. Il correspond au dossier\n<code>dist</code> qu'on retrouve dans beaucoup d’arborescences d’applications. C’est dans\ntous les autres dossiers que va se dérouler le développement.</p>\n<p>Le dossier <code>static</code> est destiné à héberger les contenus statiques comme les\nimages, les fichiers CSS et JS mais aussi les fichiers audio, vidéo, les slides\nde présentations, etc. Je passe pas mal de temps à travailler dans ce dossier.</p>\n<aside class=\"note note-info\"><p>Après être intervenue sur le redesign de Smashing Magazine,\nj'ai appris que votre structure peut-être différente de celle présentée plus\nhaut. C’est à peu près la même chose mais si vous utilisez un modèle comme\nVictor Hugo de Netlify, votre configuration sera légèrement différente, mais\nc'est du pareil au même pour ce qui est compilé et vers où. Notez que l’adoption\ndu modèle Victor Hugo est un bon moyen de commencer à intégrer Webpack et Gulp\ndans votre workflow. En ce qui me concerne je n'ai pas vraiment besoin de\nWebpack sur mon site vu le peu de JS que j'utilise, mais si vous en avez\nl’utilité, je vous recommande d’utiliser leur template pour Hugo. Et perso, je\npréfère commencer de zéro pour apprendre et comprendre comment tout ça marche.\nFaites comme bon vous semble.</p></aside>\n<h4 id=\"creer-et-mettre-en-page-du-contenu\">Créer et mettre en page du contenu</h4>\n<p>Pour chaque type de contenu dont vous avez besoin, que ce soit une page, un\nbillet de blog, un index de vos articles, de vos études de cas, etc. vous allez\ndevoir créer un fichier Markdown (<code>.md</code>) dans le dossier <code>/content/</code>. C’est là\noù sont stockés <em>tous</em> les contenus. Après avoir créé le contenu dans son\nrépertoire spécifique, vous allez créer ou réutiliser un modèle de mise en page\nstocké dans le dossier <code>/layouts/</code>.</p>\n<p>Chaque fichier <code>.md</code> du dossier <code>/content/</code> correspond à une page qui commence\navec 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>.\nPuisque je voulais m'imprégner d’un nouvel environnement et que la plupart de la\ndocumentation et des ressources dédiées à Hugo utilisent le format <code>toml</code>, c'est\nle 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>\n<aside class=\"note note-info\"><p>Je ne rentrerai pas ici sur les différences entre les deux\nformats, la documentation d’Hugo et Google sont vos amis. Personnellement ça m'a\npris un peu de temps pour apprendre à utiliser toutes ces nouvelles syntaxes\n(TOML, les modèles de template en Go, etc.) avant de me sentir à l’aise.\nNéanmoins la courbe d’apprentissage est assez rapide, ne vous laissez donc pas\nintimider par ces nouvelles syntaxes si tout cela est nouveau pour vous.</p></aside>\n<h5 id=\"definir-ou-declarer-les-types-de-contenu\">Définir (ou déclarer) les types de contenu</h5>\n<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\ncontenu qui à son tour définit le type de modèle qui sera utilisé pour le rendu.\nLe type de page est défini par la variable <code>type</code>. Par exemple le front matter\nd’un article dans la section blog de mon site ressemble à ça:</p>\n<pre><code class=\"language-toml hljs ini\">+++\n<span class=\"hljs-attr\">type</span> = <span class=\"hljs-string\">\"blog\"</span>\n<span class=\"hljs-attr\">description</span> = <span class=\"hljs-string\">\"…\"</span>\n<span class=\"hljs-attr\">title</span> = <span class=\"hljs-string\">\"…\"</span>\n<span class=\"hljs-attr\">date</span> = …\n…\n+++</code></pre>\n<p>La valeur <code>type</code> peut prendre pratiquement n'importe quelle\nvaleur, et c'est là où on peut se rendre compte du pouvoir\nd’Hugo. Vous pouvez définir autant de types de contenus que vous voulez. Par\nexemple, j'utilise actuellement cinq types de contenus pour mon site :\n<em>statique</em> (pour les pages comme \"À propos\" et \"Travailler avec moi\"), <em>blog</em>\n(pour les articles comme celui que vous êtes en train de lire), <em>ateliers</em>,\n<em>études de cas</em> et <em>bureau</em> (un nouveau type d’articles à paraître bientôt). Je\npeux créer autant de types de contenu que je veux.</p>\n<aside class=\"note note-update\"><p>Il est possible de créer des sous-sections de contenu\ndepuis la version 0.24 d’Hugo ! Cela vous permet par exemple de créer des\nsous-sections <em>design</em> et <em>développement</em> dans la section <em>articles</em> et bien\nplus. C’est une fonctionnalité intéressante.</p></aside>\n<p>C’est une des choses que j'aime chez Hugo comparativement à Jekyll qui, <em>à ma\nconnaissance</em>, n'offre pas de fonctionnalité similaire.<sup id=\"fnref1:3\"><a href=\"#fn:3\" class=\"footnote-ref\">3</a></sup></p>\n<p>La capture d’écran ci-contre montre à quoi ressemble mon dossier <code>/content/</code> en\nce moment :</p>\n<figure>\n<picture title=\"Le contenu du dossier content de mon site.\">\n<source type=\"image/webp\" srcset=\"/d33wubrfki0l68.cloudfront.net/32450b106a26b69980db6e73094c9411c5734a61/ff4f7/images/article-assets/hugo-netlify/content-types.f527c820f14db9af68380b0e37e511da.webp\" width=\"442\" height=\"726\">\n<source type=\"image/avif\" srcset=\"/d33wubrfki0l68.cloudfront.net/32450b106a26b69980db6e73094c9411c5734a61/ff4f7/images/article-assets/hugo-netlify/content-types.f527c820f14db9af68380b0e37e511da.avif\" width=\"442\" height=\"726\">\n<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;\">\n</picture>\n<figcaption>Le contenu du dossier <code>content</code> de mon site.</figcaption>\n</figure>\n<p>Les pages statiques sont créées dans des fichiers individuels au format Markdown\nà la racine du dossier <code>/content/</code>. Les autres types de contenus qui auraient\nbesoin d’un index (comme des articles, des ateliers, des études de cas, etc.)\nsont créés dans des dossiers nommés d’après le type de contenu. Par exemple on\nstockera les contenus de type <em>ateliers</em> dans un dossier <code>/content/ateliers/</code>.\nMes articles se trouvent dans le répertoire <code>/content/blog/</code>. {{&lt; marker &gt;}}Les\ndossiers de ce type sont également appelés des <code>sections</code>.{{&lt; /marker &gt;}}</p>\n<p>Pour chaque contenu, il vous faut définir son type. Vous pouvez faire ça de deux\nmanières.</p>\n<p>Le type pour les pages statiques est défini à l’aide de la variable <code>type</code> dans\nl’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,\nateliers, études de cas et bureau) est quant à lui défini à l’aide de\nl’arborescence de dossiers. Vous n'avez pas besoin de spécifier le type dans le\nfront matter lorsque vous vous reposez sur l’arborescence de fichiers. Par\nexemple un billet de blog qui se trouve dans le dossier <code>/content/blog/</code> sera\nautomatiquement traité comme un type de contenu <code>blog</code>. Inutile de le préciser\ndans le front matter de chaque article.</p>\n<p>Vous pouvez choisir de définir le type de contenu à l’aide du front matter ou de\nl’arborescence de fichier. Généralement vous utiliserez la variable <code>type</code> pour\nles pages statiques et vous vous reposerez sur l’arborescence de fichiers pour\nles contenus qui auront besoin d’un index, par exemple des billets de blog.</p>\n<p>Une chose importante à savoir est que {{&lt; marker &gt;}}si vous définissez le type\nde page à l’aide de la variable <code>type</code>, la page peut se trouver n'importe où\ndans le dossier <code>/content/</code>, l’arborescence n'aura alors aucune importance.{{%\n/marker %}}</p>\n<p>Vous pourriez donc attribuer le type <code>static</code> à une page et la placer dans le\ndossier <code>blog</code> et Hugo la considérera comme une page statique et ne tiendra pas\ncompte de sa place dans l’arborescence.</p>\n<p>Mais… pourquoi donc ? Réponse : pour choisir le type de modèle à utiliser.</p>\n<p>Voyez-vous, chaque type de contenu est associé avec un certain type de mise en\npage. Vous pouvez également utiliser un même modèle pour plusieurs types de\ncontenu. Nous verrons cela dans la partie suivante. Mais d’abord, créons\nquelques pages de contenus : deux pages statiques (<em>Accueil</em> et <em>À propos</em> par\nexemple) et une page d’index pour les articles de blog.</p>\n<p>Avant de faire cela, j'aimerais préciser quelque chose quant à la création de\npages d’index pour différentes sections ou types de contenu.</p>\n<p>La section blog nécessite la présence d’un fichier <code>_index.md</code> dans le dossier\n<code>/content/blog/</code>. C’est le fichier d’index pour cette section (celui grâce\nauquel nous afficherons la liste de tous les articles). Le dossier\n<code>/content/blog/</code> hébergera également tous les billets de blog. La capture\nd’écran suivante montre cela de façon plus visuelle :</p>\n<figure>\n<picture title=\"Le contenu du dossier /content/blog/.\">\n<source type=\"image/webp\" srcset=\"/d33wubrfki0l68.cloudfront.net/37bc25dc5366c0b251c5b2c50edd8ca246b85f4f/36428/images/article-assets/hugo-netlify/section-type.7f63ffb725507e545a3ad5d7a55893c6.webp\" width=\"472\" height=\"484\">\n<source type=\"image/avif\" srcset=\"/d33wubrfki0l68.cloudfront.net/37bc25dc5366c0b251c5b2c50edd8ca246b85f4f/36428/images/article-assets/hugo-netlify/section-type.7f63ffb725507e545a3ad5d7a55893c6.avif\" width=\"472\" height=\"484\">\n<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;\">\n</picture>\n<figcaption>Le contenu du dossier <code>/content/blog/</code>.</figcaption>\n</figure>\n<p>Chaque type de contenu qui utilise cette arborescence de dossiers (ou chaque\n<em>section</em> de contenu) comporte une page d’index qui commence par un tiret bas\n(<code>_</code>) en plus des fichiers de cette section. De la même manière, tout autre type\nde contenu (ou section) comportera aussi un index et des fichiers pour cette\nsection.</p>\n<p>OK, créons maintenant quelques pages.</p>\n<h6 id=\"la-page-d-accueil\">La page d’accueil</h6>\n<p>La page d’accueil se crée en plaçant un fichier nommé <code>_index.md</code> dans le\ndossier <code>/content/</code> comme vous pouvez le voir dans la capture d’écran un peu\nplus haut.</p>\n<p>La page d’accueil est un peu spéciale, c'est la seule de toutes les autres pages\nqui nécessite d’avoir son propre modèle de mise en page dans le dossier\n<code>/layouts/</code> – nous parlerons de ces modèles plus en détail dans la prochaine\nsection – et ce modèle de mise en page se nomme aussi <code>index.html</code>.</p>\n<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\n<code>/content/_index.md</code> et vous lui attribuez un titre ainsi qu'une description.</p>\n<p>Le front matter de ma page d’accueil ressemble à ça :</p>\n<pre><code class=\"language-go-html-template hljs go\">+++\n<span class=\"hljs-keyword\">type</span> = <span class=\"hljs-string\">\"page\"</span>\ntitle = <span class=\"hljs-string\">\"Accueil\"</span>\ndescription = <span class=\"hljs-string\">\"Sara Soueidan — Développeuse Web Front-end, auteure et conférencière\"</span>\n+++</code></pre>\n<p>La description est utilisée dans le fichier partiel d’entête du site en tant que\nvaleur de l’attribut <code>&lt;title&gt;</code> ainsi :</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;title&gt; {{ .Page.Description }} &lt;/title&gt;</code></pre>\n<p>La raison pour laquelle je n'utilise pas la valeur du <code>title</code> dans le front\nmatter pour la balise HTML <code>&lt;title&gt;</code> est que dans les autres pages, le <code>title</code>\nde la page est aussi utilisé comme intitulé de lien dans le menu principal de\nnavigation. Mais nous verrons tout ça plus tard.</p>\n<p>Les fichiers Markdown (<code>.md</code>) peuvent contenir du Markdown et du HTML et, comme\npour la page d’accueil, je n'ai aucune entrée dynamique (comme une liste\nd’articles), elle contient juste le code HTML de la page. Mais comment ce code\nMarkdown et HTML sont-ils mis en forme ? Et comment fait-on pour inclure un\nentête et un pied de page ? Tout cela se passe dans le modèle de mise en page.</p>\n<p>Le fichier <code>/layouts/index.html</code> est la mise en page utilisée pour l’accueil et\nvoici à quoi il ressemble :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"homepage-header.html\"</span> . }}\n\n{{ .Content }}\n\n{{ partial <span class=\"hljs-string\">\"footer.html\"</span> . }}</code></pre>\n<p><code>{{ .Content }}</code> récupère le contenu de la page correspondante\ndans le dossier <code>/content/</code>. Donc ici ça récupère le contenu de\nla page d’accueil à partir du fichier <code>/contents/_index.md</code>.</p>\n<p>En outre, j'appelle l’entête ainsi que le pied de page à l’aide de fichiers\npartiels.</p>\n<p>Par défaut, quand vous demandez <code>partial \"footer.html .\"</code>, Hugo va\nregarder s'il existe un fichier partiel dans le dossier <code>partials</code> situé dans le\nrépertoire <code>layouts</code>.</p>\n<p>Reportez-vous à\n<a href=\"https://gohugo.io/templates/partials/\" target=\"_blank\" rel=\"noopener noreferrer\">la documentation d’Hugo sur les fichiers partiels</a>\npour savoir ce que veut dire le point à la fin, ce qu'il fait et comment on peut\npersonnaliser les appels à des fichiers partiels.</p>\n<p>Et voilà comment on crée une page d’accueil pour son site : un fichier\n<code>/content/_index.md</code> qui contient le contenu de la page d’accueil, lui-même mis\nen page à l’aide du fichier <code>/layouts/index.html</code>.</p>\n<h6 id=\"ajouter-une-page-statique\">Ajouter une page statique</h6>\n<p>Une fois la page d’accueil terminée, j'ai voulu m'occuper du reste des pages\nstatiques avant de passer à des contenus plus dynamiques. Je me suis donc mise à\nbâtir la page <em>À propos</em>.</p>\n<p>J'ai dû faire pas mal de recherches et lire quelques fils de discussions d’aide\nsur le forum d’Hugo et ailleurs pour y parvenir. J'espère donc que ce billet\nvous sera bénéfique si vous cherchez à créer des pages statiques, ce qui s'avère\nétonnement simple en fait.</p>\n<p>Les pages statiques sont créées à la racine du répertoire <code>/content/</code>, tout\ncomme la page d’accueil. Toutefois, contrairement à la page d’accueil, les noms\nde fichiers ne commencent pas par un tiret bas.</p>\n<p>Et contrairement à la page d’accueil, vous allez devoir définir le type de page\net dire à Hugo de l’inclure dans le menu principal du site, en lui attribuant un\ntitre et une description.</p>\n<p>Pour la page <em>À propos</em> de mon site, j’ai créé un fichier <code>/content/about.md</code>.\nLe front matter de la page est le suivant :</p>\n<pre><code class=\"language-toml hljs ini\">+++\n<span class=\"hljs-attr\">type</span> = <span class=\"hljs-string\">\"static\"</span>\n<span class=\"hljs-attr\">page</span> = <span class=\"hljs-string\">\"static/single.html\"</span>\n<span class=\"hljs-attr\">title</span> = <span class=\"hljs-string\">\"À propos\"</span>\n<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>\n<span class=\"hljs-attr\">menu</span> = <span class=\"hljs-string\">\"main\"</span>\n<span class=\"hljs-attr\">weight</span> = <span class=\"hljs-string\">\"1\"</span>\n+++</code></pre>\n<p>Notez la valeur de <code>type</code>. Comme dit plus haut, vous pouvez attribuer ici la\nvaleur de votre choix. J'ai choisi <code>static</code>, car ça décrit littéralement le type\nde la page. Et aussi parce qu'on trouve beaucoup de ressources en ligne qui\nutilisent ce type pour les pages statiques.</p>\n<p>La variable <code>page</code> indique à Hugo quel modèle de mise en page présent dans le\nrépertoire <code>/layouts/</code> utiliser.</p>\n<aside class=\"note note-info\"><p>Il est bon de noter également que Hugo utilisera automatiquement ce modèle même\nsi je ne lui dis pas. Je me rappelle tout de même avoir eu quelques prises de\ntête au début quand j'essayais de comprendre comment utiliser les modèles pour\nles différentes pages. Je ne savais pas quel modèle allait être utilisé. Même en\nayant lu la documentation, je me suis retrouvée à faire et défaire pas mal de\nchoses pour m'apercevoir que les choses marchaient comme par magie, ou pas du\ntout. Au début, Hugo ressemblait à une boîte noire pour moi et il m'a fallu\nquelques jours pour en comprendre assez et pour oser écrire à son sujet. Quand\nça a fini par fonctionner, j'ai décidé de ne plus toucher au front matter, car\nj'avais peur de casser une fois de plus ma mise en page. Mais maintenant que\nj'en sais davantage, il est bon de signaler que vous n'avez pas vraiment besoin\nde la variable <code>page</code> ici.</p></aside>\n<p>Le <code>title</code> est utilisé comme intitulé de lien dans le menu. (Sur mon site le\nmenu situé en haut de page contient une entrée \"About &amp; Interviews\").</p>\n<p>Je vous ai déjà dit que la <code>description</code> est utilisée dans le fichier partiel\nqui gère l’entête de page, cette description apparait ensuite dans l’onglet de\nvotre navigateur.</p>\n<p>La variable <code>menu</code> indique à Hugo que cette page doit avoir une\nentrée dans le menu principal.</p>\n<p>La variable <code>weight</code> est très utile pour vous aider à définir\nl’ordre d’affichage des liens dans le menu. Si vous ne l’utilisez\npas, Hugo utilisera son propre ordre par défaut – qui n'était pas celui que je\nsouhaitais pour mon site. Vous pouvez également définir des valeurs négatives\npour cette variable.</p>\n<aside class=\"note note-info\"><p>Pour faire court, je vous renvoie une fois de plus à la\ndocumentation d’Hugo pour ce qui est de l’utilisation et de la configuration du\nmenu principal. J'ajoute que certains aspects sont encore assez confus pour moi,\nmais comme je suis arrivée à faire ce que je voulais maintenant : je ne touche\nplus à rien, j'ai trop peur de casser un truc. Une fois de plus. 😂</p></aside>\n<p>Toutes les autres pages statiques sont créées de la même manière. La seule chose\nqui change c'est le titre, la description et leur ordre dans le menu. Elles\nutilisent toutes le même modèle de mise en page.</p>\n<p>Je me note quelque chose ici pour plus tard :</p>\n<p><strong>Hugo respecte un ordre spécifique pour décider du modèle de mise en page à\nutiliser pour chaque page créée dans le dossier <code>/content/</code>. Nous en reparlerons\ndans la section dédiée aux modèles juste après. Donc si nous n'avions pas défini\nle fichier <code>/layouts/static/single.html</code> comme étant le modèle à utiliser, Hugo\naurait utilisé un modèle par défaut stocké dans <code>/layouts/</code>. Nous y\nreviendrons</strong>.</p>\n<p>Enfin, comme pour la page d’accueil, le contenu HTML de la page <em>À propos</em> se\ntrouve dans le fichier <code>about.md</code> puis il est ensuite inséré dans le modèle\n<code>/layouts/static/single.html</code> à l’aide de <code>{{ .Content }}</code>. Nous faisons aussi\nappel aux fichiers partiels d’entête et de bas de page. Notez la correspondance\nentre le type <code>static</code> et le dossier <code>static</code> situé dans <code>layouts</code> qui contient\nle modèle de mise en page.</p>\n<aside class=\"note note-info\"><p>Vous n'avez pas à écrire tout le HTML dans le fichier\nMarkdown. Vous pouvez mettre toute la structure du HTML, comme les conteneurs,\netc. dans le modèle de mise en page et n'avoir que le texte dans le fichier\nMarkdown. Si j'ai procédé de la sorte, c'est juste que ça me convient bien comme\nça.</p></aside>\n<h5 id=\"les-archetypes-de-contenu\">Les archétypes de contenu</h5>\n<p>Vous avez peut-être remarqué sur la capture d’écran plus haut que j'ai aussi un\ndossier nommé <code>/archetypes/</code> à la racine de mon site. Ce dossier est lui aussi\nlié aux types de contenu que vous créez. Mais il a un but bien précis.</p>\n<p>Pour expliquer à quoi sert ce répertoire, je vais commencer par citer\n<a href=\"https://hugodocs.info/content-management/archetypes/\" target=\"_blank\" rel=\"noopener noreferrer\">la page correspondante de la documentation d’Hugo</a> :</p>\n<blockquote>\n<p>Les archétypes vous permettent de créer de nouvelles instances de types de\ncontenu et de définir des paramètres par défaut à partir de la ligne de\ncommande.</p>\n<p>Les archétypes sont des fichiers de contenu stockés dans le répertoire\n<code>archetypes</code> de votre projet, qui contiennent un front matter pré-configuré\npour les types de contenu de votre site web. Les archétypes facilitent la\nconsistance des métadonnées des contenus à travers tout votre site et\npermettent aux auteurs de générer rapidement de nouvelles instances de type de\ncontenu à l’aide de la commande <code>hugo new</code></p>\n<p>Hugo est capable de déduire l’archétype approprié à l’aide de la section de\ncontenu passée en argument de la commande <code>new</code> :</p>\n<p><code>hugo new &lt;section-de-contenu&gt;/&lt;nom-de-fichier.md&gt;</code></p>\n</blockquote>\n<p>En d’autres mots, définir un archétype vous permet de créer de nouveaux contenus\nplus rapidement, puisqu'il va remplir le front matter de notre nouvelle page\navec les variables de votre choix.</p>\n<p>Par exemple, supposons que je veuille créer une nouvelle étude de cas (qui irait\ndans <code>/content/etudes-de-cas/</code>). Au lieu de créer un nouveau fichier Markdown\ndans le répertoire, je peux taper cette commande dans le terminal et Hugo va\ncréer le nouveau fichier pour moi :</p>\n<pre><code class=\"language-sh hljs bash\">hugo new etudes-de-cas/ma-nouvelle-etude-de-cas.md</code></pre>\n<p>Et les variables de cette nouvelle étude de cas (<code>ma-nouvelle-etude-de-cas.md</code>)\nseront automatiquement ajoutées : nom du client, logo du client (chemin vers\nl’image), description du client, description du projet, date du projet, etc. Par\ndéfaut les valeurs de ces variables seront vierges, prêtes à être renseignées.</p>\n<p>La capture d’écran suivante montre les variables front matter que j'ai définis\npour l’archétype <code>etudes-de-cas</code> :</p>\n<figure>\n<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.\">\n<source type=\"image/webp\" srcset=\"/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346514/archetype-hugo.55dc72733c09de2b5b4cb1ac27ac5e81.webp\" width=\"711\" height=\"594\">\n<source type=\"image/avif\" srcset=\"/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346514/archetype-hugo.55dc72733c09de2b5b4cb1ac27ac5e81.avif\" width=\"711\" height=\"594\">\n<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;\">\n</picture>\n<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>\n</figure>\n<p>Notez aussi que les autres archétypes que j'ai définis dans le répertoire\n<code>archetypes</code> qui correspondent aux quatre autres types de section qui figurent\nsur mon site. C’est à peu près tout ce qu'il faut savoir sur les archétypes. Si\nvous souhaitez en savoir plus, reportez-vous à la page dédiée dans la\ndocumentation d’Hugo. C’est bien expliqué. Vous n'êtes pas obligés de définir\ndes archétypes, mais je pense que vous en aurez envie.</p>\n<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>\n<p>C’est la partie avec laquelle j'ai eu le plus de mal au début. Comment est-ce\nque je sais que tel modèle est utilisé pour telle section ? Comment est-ce que\nje sais de combien de modèles j'ai besoin ? Et est-ce qu'il y a vraiment besoin\nde modèle ?</p>\n<p>J'ai pas mal trifouillé et cherché sur le net, puis j'ai passé le plus clair de\nmon temps à faire des essais, jusqu'à avoir des modèles qui fonctionnent bien.\nPuis j'ai tout cassé et refait les choses pour comprendre quand et comment ça\nfonctionnait. Je peux maintenant affirmer avec assurance que j'ai bien compris\ntout ça.</p>\n<p>En général, pour un blog très simple, vous n'aurez besoin que de deux modèles\npar défaut : <code>list.html</code> et <code>single.html</code>.</p>\n<p>Le modèle <code>list.html</code> aura pour mission d’afficher des listes d’éléments, comme\nsur la page d’index où sont affichées la liste de vos billets de blog.</p>\n<p>Quant au modèle <code>single.html</code>, comme vous l’aurez deviné, il servira pour mettre\nen forme les pages uniques comme celle d’un billet de blog.</p>\n<p>Ces deux modèles doivent se trouver dans le répertoire <code>/layouts/_defaults/</code>.</p>\n<p>Ainsi, si vous créez un blog avec quelques articles et ne donnez aucune\ninstruction particulière à Hugo à propos de leur mise en page, il ira voir dans\nle dossier <code>/layouts/_defaults/</code> quels modèles utiliser.</p>\n<p>J'ai mis en place ces modèles comme solution par défaut sur mon blog, mais je\nles <em>surcharge</em>.</p>\n<p>Vous pouvez surcharger les modèles par défaut en fournissant des modèles qui\nporteront le même nom que votre section ou votre type de contenu.</p>\n<p>En d’autres termes, vous pouvez créer dans le répertoire <code>/layouts/</code> une\nstructure de dossiers similaire à celle que vous avez dans le répertoire\n<code>/content/</code> et Hugo se basera sur cette structure pour déterminer le modèle à\nutiliser.</p>\n<p>Ou alors vous pouvez créer un répertoire du même nom que le <code>type</code> que vous avez\ndéfini, comme <code>static</code> par exemple que j'utilise pour les pages statiques.\nPlutôt que d’utiliser le modèle par défaut, Hugo utilisera alors le modèle situé\ndans le répertoire <code>/layouts/static/</code> pour toutes les pages qui auront le\n<code>type = static</code>.</p>\n<p>J'ai pour ma part créé le fichier <code>/layouts/static/single.html</code> que Hugo va\nutiliser pour surcharger la mise en page des pages statiques\n<code>/layouts/_default/single.html</code> .</p>\n<p>Encore une fois la page <code>/layouts/static/single.html</code> est simplement un modèle\navec le contenu suivant :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ partial <span class=\"hljs-string\">\"header.html\"</span> . }}\n\n{{ .Content }}\n\n{{ partial <span class=\"hljs-string\">\"footer.html\"</span> . }}</code></pre>\n<p>dans lequel le contenu est récupéré à partir des fichiers Markdown respectifs.\nDonc la page <code>about.html</code> est générée à l’aide du modèle de page\n<code>/layouts/static/single.html</code> et <code>{{ .Content }}</code> est remplacé par le contenu du\nfichier <code>/content/about.md</code>.</p>\n<p>Maintenant pour créer une page d’index pour une liste d’éléments, comme la page\nde blog et les articles listés ou la page d’ateliers et les pages de détails des\nateliers, on procède de manière très similaire.</p>\n<p>De la même manière que nous avons créé un répertoire pour le type de contenu qui\nporte le même nom que le <code>type</code> lui-même, nous créons un répertoire pour chaque\nautre type de contenu que nous avons défini à l’aide de notre arborescence de\ndossiers et nous donnons à ce répertoire le même nom que celui du dossier\nprésent dans le dossier <code>content</code>.</p>\n<p>Ou si vous préférez : de la même manière que nous avons créé un dossier dans le\nrépertoire <code>layouts/</code> du même nom que le <code>type</code> de contenu, nous créons un\ndossier pour chaque section de contenu (<code>blog</code>, <code>ateliers</code>, <code>etudes-de-cas</code>,\netc.) de manière à obtenir une structure de dossiers similaire dans <code>layouts</code> à\ncelle que nous avons dans <code>/content/</code>.</p>\n<p>C’est toujours pas clair ? Alors regardez ce que ça donne pour mon site :</p>\n<figure>\n<picture title=\"La structuration des répertoires pour le contenu et les modèles de mon site.\">\n<source type=\"image/webp\" srcset=\"/d33wubrfki0l68.cloudfront.net/1e4417080932df239c9a7eae7ded8f0ad59eb2ea/7ae87/images/article-assets/hugo-netlify/layouts.befc688570e542aa30c368ba7bf2a4c6.webp\" width=\"474\" height=\"1270\">\n<source type=\"image/avif\" srcset=\"/d33wubrfki0l68.cloudfront.net/1e4417080932df239c9a7eae7ded8f0ad59eb2ea/7ae87/images/article-assets/hugo-netlify/layouts.befc688570e542aa30c368ba7bf2a4c6.avif\" width=\"474\" height=\"1270\">\n<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;\">\n</picture>\n<figcaption>La structuration des répertoires pour le contenu et les modèles de mon site.</figcaption>\n</figure>\n<p>Attardons-nous à nouveau quelques instants sur la section blog. Au répertoire\n<code>/content/blog/</code> correspond le répertoire <code>/layouts/blog/</code>.</p>\n<p>À l’intérieur du répertoire <code>/content/blog/</code> se trouve la page d’index\n<code>_index.md</code> et les articles de blog.</p>\n<p>Dans <code>/layouts/blog/</code> nous avons le modèle <code>list.html</code> ainsi que celui de la\npage <code>single.html</code>.</p>\n<p>Hugo utilisera le modèle <code>list.html</code> pour la page <code>_index.md</code> et le modèle\n<code>single.html</code> pour chacun des articles de blog.</p>\n<p>De la même manière, toutes les autres sections possèdent leur propre répertoire\nde modèles, qui contient les modèles <code>list.html</code> et <code>single.html</code>.</p>\n<p>Encore une fois vous n'avez pas réellement besoin de tous ces modèles. Et vous\naurez peut-être remarqué que quelques-unes des pages sont en tout point\nsimilaires à l’exception de leur nom. Si je fais ça, c'est uniquement pour des\nraisons de flexibilité future. Si jamais je veux changer le modèle de l’un des\ntypes de section, j'aurai simplement à modifier son modèle correspondant. Si\nvotre site est plus simple et n'utilise pas autant de types de contenus, vous\nn'avez surement pas besoin de créer autant de modèles que moi.</p>\n<p>La seule exception à la structuration des répertoires de modèles c'est la page\nd’accueil, dont le modèle de mise en page est placé à la racine du répertoire\n<code>/layouts/</code> et se nomme <code>index.html</code>.</p>\n<p>Il est important de vérifier l’ordre dans lequel Hugo va choisir le modèle à\nutiliser pour chaque page. Je vous le recommande vivement.</p>\n<p>Pour citer la documentation :</p>\n<blockquote>\n<p>Hugo obéit à plusieurs règles pour savoir quel modèle utiliser pour effectuer\nle rendu d’une page spécifique. Hugo va utiliser la liste priorisée suivante.\nSi un fichier n'est pas présent, alors on utilisera le suivant dans la liste.\nCela vous permet de concevoir des modèles particuliers quand vous le souhaitez\nsans devoir créer plus de modèles que nécessaire. Pour la plupart des sites,\nseul le fichier <code>_default</code> en fin de liste sera nécessaire. Les utilisateurs\npeuvent spécifier le type et le modèle dans le front matter. La section est\ndéterminée en fonction de l’endroit où se trouve le fichier de contenu. Si le\ntype est fourni, il sera utilisé à la place de la section.</p>\n</blockquote>\n<p>Vous en apprendrez davantage sur cet ordre de priorisation dans\n<a href=\"https://hugodocs.info/content-management/organization/\" target=\"_blank\" rel=\"noopener noreferrer\">la page qui documente l’organisation des contenus</a>.</p>\n<h4 id=\"boucler-sur-les-listes-de-section\">Boucler sur les listes de section</h4>\n<p>Le dernier point technique sur Hugo que je veux aborder concerne le listing des\narticles d’une section sur la page d’index de cette section.</p>\n<p>Une fois de plus, basons-nous sur l’exemple de la section blog située dans\n<code>/content/blog/</code>.</p>\n<p>Les fichiers Markdown ne contiennent bien entendu aucune logique de modèle. Donc\npour lister tous les billets de blog, nous allons devoir faire cela dans le\nmodèle correspondant à cette page d’index, situé dans <code>/layouts/blog/list.html</code>.\nLa boucle et toute la logique de modèle est écrite à l’aide du\n<a href=\"https://hugodocs.info/templates/introduction/\" target=\"_blank\" rel=\"noopener noreferrer\">templating HTML du langage Go</a>.</p>\n<p>La boucle en elle-même pourra et sera probablement différente pour la majorité\nd’entre vous. Après avoir pas mal cherché, je suis arrivée à écrire la boucle\nsuivante qui affiche les cinq derniers articles, suivi d’un appel à un fichier\npartiel pour la gestion de la pagination.</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;ul class=<span class=\"hljs-string\">\"articles-list\"</span>&gt;\n    &lt;!-- Boucle à travers les fichiers situés dans content/blog<span class=\"hljs-comment\">/*.md --&gt;\n    {{ range (.Paginator 5).Pages }}\n    &lt;li class=\"post\"&gt;\n        &lt;a class=\"post-title\" href=\"{{.RelPermalink}}\"&gt;{{ .Title }}&lt;/a&gt;\n        &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;\n\n        &lt;div class=\"post-summary\"&gt;\n            {{ .Summary }} &lt;!-- extrait automatiquement le premier paragraphe du fichier Markdown de l’article --&gt;\n        &lt;/div&gt;\n\n        &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;\n    &lt;/li&gt;\n\n    {{ end }}\n&lt;/ul&gt;\n\n{{ partial \"pagination.html\" . }}</span></code></pre>\n<aside class=\"note note-info\"><p>Ne faites pas attention au code HTML de cette boucle, ça fait\nun moment que je n'ai pas travaillé sur mon site, il aurait bien besoin de\nquelques améliorations. Le balisage sera bientôt mis à jour.</p></aside>\n<p>C’est la partie <code>{{ range .Paginator.Pages }}</code> qui est vraiment importante ici.\n{{&lt; marker &gt;}}Chaque <code>.Paginator</code> que vous utilisez dans une page d’index de\nsection va boucler et afficher les articles <strong>de cette section</strong>.{{&lt; /marker &gt;}}\n<code>(.Paginator 5).Pages</code> indique à Hugo de ne lister que cinq éléments. Cette\nboucle va parcourir tous les articles de la section <code>blog</code> et ne lister que les\ncinq plus récents. Une boucle similaire dans le fichier\n<code>layouts/workshops/index.html</code> bouclerait sur les ateliers stockés dans le\ndossier <code>/content/workshops/</code> et afficherait la liste des ateliers dans l’index.</p>\n<aside class=\"note note-info\"><p>Je confonds encore quelques variables globales du site et des\nvariables de page dans Hugo. Ce que j'ai pour le moment me suffit, et si jamais\nj'avais besoin de plus de flexibilité, d’options ou de fonctionnalités, il\nfaudra que je me replonge de nouveau dans la documentation pour arriver à tirer\nde la logique d’Hugo plus qu'une simple boucle. Vous devriez en faire de\nmême.</p></aside>\n<p>Et pour ce qui est du fichier partiel <code>pagination.html</code>, le mien ressemble pour\nle moment à ça :</p>\n<pre><code class=\"language-go-html-template hljs go\">{{ $baseurl := .Site.BaseURL }}\n{{ $pag := .Paginator }}\n\n{{ <span class=\"hljs-keyword\">if</span> gt $pag.TotalPages <span class=\"hljs-number\">1</span> }}\n\n&lt;nav class=<span class=\"hljs-string\">\"center pagination\"</span>&gt;\n    {{ <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 }}\n\n    &lt;div class=<span class=\"hljs-string\">\"clearfix\"</span>&gt;\n        {{ <span class=\"hljs-keyword\">if</span> .Paginator.HasPrev }}\n        &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;\n            Articles plus récents\n        &lt;/a&gt;\n        {{ <span class=\"hljs-keyword\">else</span> }}\n        &lt;span class=<span class=\"hljs-string\">\"pagination__button pagination__button--previous button--disabled\"</span>&gt;Articles plus récents&lt;/span&gt;\n        {{ end }}\n\n        {{ <span class=\"hljs-keyword\">if</span> .Paginator.HasNext }}\n        &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;\n            Articles plus anciens\n        &lt;/a&gt;\n        {{ <span class=\"hljs-keyword\">else</span> }}\n        &lt;span class=<span class=\"hljs-string\">\"pagination__button pagination__button--next button--disabled\"</span>&gt;Articles plus anciens&lt;/span&gt;\n        {{ end }}\n    &lt;/div&gt;\n\n    &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;\n\n&lt;/nav&gt;\n\n{{ end }}</code></pre>\n<p>Libre à vous d’aller en apprendre plus sur les variables. Je trouve que le code\nci-dessus est compréhensible tel quel, mais encore une fois, si vous avez besoin\nde plus de fonctionnalités, la documentation et le forum vous seront\nprobablement d’une plus grande aide.</p>\n<h4 id=\"creer-une-page-d-archive\">Créer une page d’archive</h4>\n<p>En plus de la page de blog par défaut, je voulais ajouter une page d’archive qui\nliste la totalité de mes articles sur une seule et unique page. Ce n'était pas\naussi évident que je l’aurais cru. La documentation ne m'a pas beaucoup aidée.\nEt j'ai dû à nouveau faire des recherches. Je suis tombée sur\n<a href=\"https://parsiya.net/blog/2016-02-14-archive-page-in-hugo/\" target=\"_blank\" rel=\"noopener noreferrer\">cet article extrêmement utile</a>\net j'ai eu recours à la même technique que celle exposée par l’auteur.</p>\n<p>Pour la page d’archive, j'ai créé une page statique dans <code>/content/</code> et je lui\nai donné un nouveau <code>type</code>: <code>archive</code>. La page utilise le modèle situé dans\n<code>/layouts/archive/single.html</code>.</p>\n<p>Dans le modèle de page, je boucle sur les articles comme pour la page d’index du\nblog, mais avec une différence importante :</p>\n<pre><code class=\"language-go-html-template hljs go\">&lt;!-- /layouts.archive/single.html --&gt;\n\n{{ <span class=\"hljs-keyword\">range</span> where .Site.Pages <span class=\"hljs-string\">\"Type\"</span> <span class=\"hljs-string\">\"blog\"</span> }}\n&lt;li class=<span class=\"hljs-string\">\"post\"</span>&gt;\n    &lt;a class=<span class=\"hljs-string\">\"post-title\"</span> href=<span class=\"hljs-string\">\"{{.RelPermalink}}\"</span>&gt;{{ .Title }}&lt;/a&gt;\n    &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;\n\n    &lt;div class=<span class=\"hljs-string\">\"post-summary\"</span>&gt;\n        {{ .Summary }} &lt;!-- récupère automatiquement le premier paragraphe de l’article --&gt;\n    &lt;/div&gt;\n\n    &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;\n&lt;/li&gt;\n{{ end }}</code></pre>\n<p>En résumé : <code>.Site.Pages</code> boucle sur toutes les pages de votre\nsite. En d’autres termes, cela va lister tous les fichiers Markdown contenus\ndans le dossier <code>/content/</code>. Pour indiquer à Hugo de n'afficher\nque les fichiers situés dans la section <code>/content/blog/</code>, on “filtre” les pages\nen précisant le <code>\"Type\" \"blog\"</code>. On procédera également de la sorte pour une\npage d’archive d’une autre section, en utilisant le nom de la section comme\nfiltre. Et c'est tout.</p>\n<h3 id=\"heberger-chez-netlify\">Héberger chez Netlify</h3>\n<p>J'avais choisi d’héberger mon site avec GitHub Pages depuis quelques années.\nPuis est arrivé un moment où ça commençait à faire un peu juste. Il semble qu'il\ny ait eu aussi régulièrement de curieux problèmes de cache et je devais pousser\ndeux fois les changements sur le dépôt pour que ces derniers soient pris en\ncompte (j'imagine que le cache n'était pas invalidé quand il devait l’être).\nJ'ai donc commencé à devoir créer des enregistrements vides juste pour vider le\ncache et être capable de voir les changements que j'avais faits en production.</p>\n<p>Maintenant, je ne suis pas certaine que c'était vraiment un problème de cache,\nbien que ça y ressemblait beaucoup. Je ne sais pas non plus si quelqu'un d’autre\nest capable de reproduire ce problème. Et non, je n'ai pas contacté le support\nde GitHub à ce sujet. Je détestais tellement mon site Web que je me suis dit\n\"j'ai déjà assez bien de problèmes en local pour me soucier de ce problème en\nproduction\", j'en ai donc fait totalement abstraction.</p>\n<p>J'ai pu aussi me rendre compte de l’ultra-rapidité de\n<a href=\"https://www.netlify.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Netlify</a> quand j'ai travaillé sur Smashing Magazine.\nDe plus, Netlify permet de \"rendre votre site ou votre application web bien plus\nrapide en la servant au plus près des utilisateurs. Au lieu d’un serveur unique,\nvous déployez sur un réseau global de nœuds CDN intelligents, qui gère aussi\nl’unicité des assets, la mise en cache automatique des entêtes, les redirections\net les réécritures intelligentes.\"</p>\n<p>Et en plus de tout ça, si vous êtes un développeur et que vous travaillez en\nopen source, Netlify vous offre un abonnement Pro à vie. Tout ce qu'ils\ndemandent en retour est un lien vers Netlify sur votre site ou votre\napplication. Pour moi ce ne fut pas un problème vu que je mentionne toujours où\nmon site est hébergé dans le bas de page. J'ai donc signé pour la formule Pro.\nUn hébergement gratuit et rapide ! Woohoo !</p>\n<p>La configuration de votre site se fait en quelques clics :</p>\n<ul>\n<li>Créer un compte sur <a href=\"https://netlify.com\" target=\"_blank\" rel=\"noopener noreferrer\">netlify.com</a></li>\n<li>Relier son compte Netlify à son dépôt de code. Le mien est hébergé sur GitHub,\nj'ai pu le connecter depuis l’interface de Netlify.</li>\n<li>Spécifier le dossier de destination ainsi que la commande de build,\nrespectivement <code>public</code> et <code>hugo</code> dans mon cas. (Voir les captures d’écrans\nci-dessous)</li>\n<li>Configuration de votre nom de domaine. Cela demande de faire quelques\nchangements de DNS.</li>\n<li>Cela m'a demandé seulement 3 clics pour bénéficier d’un certificat SSL\nrenouvelé automatiquement et d’une connexion HTTPS pour mon site.</li>\n<li>Et… c'est tout.</li>\n</ul>\n<p>Je devrais probablement mentionner le fait que j'ai rencontré quelques\ndifficultés lorsque j'ai fait la bascule, mais ce n'était pas de la faute de\nNetlify. L'équipe de Netlify a même été super et m'a aidée à déboguer les\nproblèmes que je rencontrais. Après avoir effectué les changements dans la\nconsole du registrar de mon domaine, cela a pris quelques heures pour que mon\nsite soit en ligne avec mon nom de domaine personnalisé.</p>\n<p>Quelques bons trucs à savoir :</p>\n<ul>\n<li>Ajouter votre dossier <code>/public/</code> à votre fichier <code>.gitignore</code>. Netlify va\nlancer la génération de votre site sur ses serveurs. Pour éviter de possibles\nconflits, ne versionnez pas votre dossier de destination dans votre dépôt. Le\nmien n'est présent que sur ma machine. Je rencontrais des problèmes de rendus\navec certains templates quand je le versionnais auparavant.</li>\n<li>Vérifiez bien la version d’Hugo que vous utilisez (<code>hugo version</code>) et celle\nutilisée par Netlify. Au début j'ai eu droit à des erreurs de build qui\nempêchaient le déploiement, car ma version était plus récente que celle de\nNetlify. Si c'est le cas\n<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>\nqui correspond à la version d’Hugo que vous utilisez localement.</li>\n</ul>\n<p>Voici en partie à quoi ressemble mon tableau de bord Netlify :</p>\n<figure>\n<picture title=\"Paramètres de déploiement et variables environnement dans le tableau de bord de Netlify.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Paramètres de déploiement et variables environnement dans le tableau de bord de Netlify.</figcaption>\n</figure>\n<p>J'aime aussi le fait que Netlify propose des options pour optimiser et assembler\nles assets pour vous, afin d’améliorer les performances globales de votre site.</p>\n<figure>\n<picture title=\"Options d’optimisation des assets dans le tableau de bord de Netlify.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Options d’optimisation des assets dans le tableau de bord de Netlify.</figcaption>\n</figure>\n<p>J'ai constaté quelques améliorations et plus de A verts sur la page de résultats\nsur <a href=\"https://webpagetest.org\" target=\"_blank\" rel=\"noopener noreferrer\">webpagetest.org</a> alors qu'ils étaient rouges\nauparavant. J'ai encore du travail de ce côté-là.</p>\n<h3 id=\"resume-de-ma-configuration-actuelle\">Résumé de ma configuration actuelle</h3>\n<ul>\n<li>Le code source du site web est hébergé sur GitHub,</li>\n<li>J'utilise Hugo comme générateur de site statique,</li>\n<li>Déploiement automatiquement à chaque <code>push</code> sur le dépôt grâce à Netlify,</li>\n<li>Hébergée gratuitement chez Netlify avec le plan Open Source.</li>\n</ul>\n<p>Il est également utile de mentionner que désormais la compilation complète de\nmon site après chaque changement, sans avoir à filtrer de vieux contenus, prend\nà Hugo moins de 40 millisecondes. {{&lt; marker &gt;}}Hugo met 39ms à compiler mon\nsite pour être plus précis{{&lt; /marker &gt;}}, là où Jekyll, même avec des options\ncomme <code>--incremental</code> mettait plusieurs <strong>minutes</strong>.</p>\n<h3 id=\"objectifs-futurs\">Objectifs futurs</h3>\n<p>On retrouve ici quelques-unes des choses qui figurent sur ma TODO liste depuis\nquelques années et que j'avais jusqu'ici remis à plus tard, en partie à cause de\nla situation dans laquelle je me trouvais précédemment avec Jekyll :</p>\n<ul>\n<li><strong>Lancer une mailing-list.</strong> C’est prévu d’ici la fin du mois.</li>\n<li>Une nouvelle section pour les articles qui ne rentrent pas dans la section des\narticles techniques.</li>\n<li>Améliorer la qualité du code du site pour ne plus être embarrassée et rendre\nle dépôt public sur Github.</li>\n<li><strong>Rendre le site disponible en mode offline.</strong> Et le rendre encore plus\n<em>rapide</em>.</li>\n<li>Il y aura une <strong>FAQ</strong> mais pas au format des AMA (Ask Me Anything) qu'on\ntrouve sur GitHub. Il y a des aspects que je n'aime pas dans ce format. Plus\nd’informations et de détails dès que la lettre d’information paraîtra.</li>\n<li><strong>Écrire plus régulièrement.</strong> Je laisse beaucoup trop d’idées de côté que je\ndevrais transformer en articles de blog. Je me suis promise d’écrire plus\nsouvent, même si ces idées d’articles ne sont pas aussi poussées que\nd’habitude. Cet article est un début.</li>\n</ul>\n<h3 id=\"quelques-mots-de-conclusion\">Quelques mots de conclusion ?</h3>\n<p>Je laisserai à Agnès le soin d’exprimer ce que je ressens vis-à-vis de cette\nnouvelle configuration, même si je sais que je peux et que j'améliorerai encore\nquelques trucs dans le futur :</p>\n<figure>\n<iframe src=\"https://giphy.com/embed/uHSbNh58qwIwM\" width=\"480\" height=\"264\" frameborder=\"0\" class=\"giphy-embed\" allowfullscreen></iframe>\n</figure>\n<p>Au moins maintenant je dispose d’un système qui m'évitera des maux de tête à\nchaque changement que je voudrais apporter à mon site Web. Je prends de nouveau\nplaisir à écrire des articles de blog, ce qui veut dire que vous pouvez vous\nattendre à de prochaines publications dans les semaines à venir.</p>\n<p>Merci de m'avoir lue jusqu'ici.</p>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n<li id=\"fn:2\">\n<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>\n</li>\n<li id=\"fn:3\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/05/29/configurer-netlify-cms-pour-jekyll/",
      "url": "https://jamstatic.fr/2017/05/29/configurer-netlify-cms-pour-jekyll/",
      "title": "Configurer Netlify CMS pour Jekyll",
      "summary": "Netlify CMS est une application web qui vous permet d’éditer vos fichiers Markdown depuis une interface visuelle. Cette application web facilite l’utilisation de générateurs de site statique pour les collaborateurs non techniques.",
      "date_published": "2017-05-29T00:00:00+00:00","content_text": "Les outils de gestion de contenus connectés aux générateurs de site statique continuent d’évoluer. Lors de la refonte de Smashing Magazine, 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 CMS headless pour faciliter la contribution des rédacteurs. Ce CMS open source est simple à configurer, cela ne vous prendra que quelques minutes. Dans cet article, nous utiliserons Jekyll, le générateur le plus populaire, sachez que le principe est similaire pour Hugo ou d’autres générateurs.\nNous partirons du principe que vous avez une installation de Jekyll déjà fonctionnelle, dans le cas contraire, reportez-vous à la documentation officielle. Nous présupposons également que vous versionnez votre projet avec Git et vous poussez votre code sur GitHub, GitLab ou BitBucket.\nConfigurer Netlify\nLa première étape est de se connecter chez Netlify afin de pouvoir relier votre dépôt Git à ce service d’hébergement et de déploiement continu. C’est gratuit et si vous travaillez sur un projet open source, vous pouvez utiliser la formule pro.\n\n\n\n\n\n\nAjout de site, étape 2 : choix du dépôt.\n\nL'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 (master dans notre cas) et la commande de build utilisée (jekyll build pour Jekyll) ainsi que le dossier de publication(_site par défaut avec Jekyll). Une fois le site configuré, nous allons pouvoir nous occuper d’autoriser l’édition de contenu via Netlify CMS.\n\n\n\n\n\n\nAjout de site, étape 3 : configuration du déploiement.\n\nAuthentification via GitHub\nMaintenant il nous faut créer une nouvelle application Oauth sur GitHub (ou le service que vous utilisez) et de mentionner https:\/\/api.netlify.com\/auth\/done comme URL de callback d’authentification. Vous donnez ainsi l’autorisation à Netlify CMS d’accéder aux fichiers du dépôt.\n\n\n\n\n\n\nConfiguration de l’application Oauth dans GitHub.\n\nAjout des fichiers de l’admin\nMaintenant que l’authentification est configurée nous pouvons ajouter un dossier admin à la racine de notre dépôt qui contiendra deux fichiers : index.html et config.yml.\n\nComme vous pouvez le voir ce fichier index.html se contente d’appeler des fichiers JS et CSS distants, le fait d’utiliser @latest vous permet de bénéficier automatiquement de la dernière version, il n'y aura donc aucune mise à jour à faire 😃.\nLe fichier config.yml contient le chemin vers votre dépôt GitHub (à adapter donc à votre cas de figure), ici l’option editorial_workflow est activée mais vous pouvez commenter la ligne si vous n'en avez pas l’utilité.\n\n\n\n\n\n\nAperçu du workflow de publication de Netlify CMS.\n\nVous pouvez préciser le dossier dans lequel vous sauvegardez vos images, ici elles vont dans le dossier assets\/images\/.\nLa dernière section collections recense les champs habituellement utilisés dans les variables Front Matter des collections que vous souhaitez pouvoir éditer dans l’interface du CMS. Vous pouvez personnaliser cette section en fonction de vos besoins et ajouter les widgets dont vous avez besoin.\n\n\n\n\n\n\nÉdition des champs personnalisés d’un article.\n\nUne 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 :\ngit add admin\ngit commit -m \"Admin de Netlify CMS\"\ngit push\nAccéder à l’administration\nCe commit va déclencher un build et un déploiement sur Netlify et vous devriez maintenant pouvoir accéder à https:\/\/votredomaine.com\/admin\/. (Notez que ça ne marchera pas en local à l’instar de plugin comme jekyll-admin).\nAprè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.\nNetlify est en train de travailler sur son Styleguide et à n'en pas douter son CMS devrait en bénéficier quand il sera plus abouti.\n\n\n\n\n\n\nLa liste des articles dans Netlify CMS.\n\nDans 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.\nSi vous souhaitez donner l’accès à plusieurs collaborateurs, rendez-vous sur app.netlify.com dans l’onglet access de votre site et ajoutez autant de collaborateurs que vous le souhaitez.\n\n\n\n\n\n\nConfiguration de l’accès au site Netlify.\n\nEt voilà !\nFé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 URL unique de prévisualisation\naccessible depuis GitHub pour chaque pull-request créée. Elle est pas belle la vie ?\n\n\n\n\n\n\nLors de la sauvegarde d’un nouvel article, une pull request est créée sur GitHub avec un lien vers une URL de prévisualisation.\n\nEt 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 la documentation du projet.\nCe 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 à Siteleaf ou Forestry, il est auto-hébergé.\nVous pouvez consulter la roadmap pour suivre l’avancée du projet.\nIl 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 les développeurs et les rédacteurs partagent un worflow commun. 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.\nNous 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.\nLa 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.",
      "content_html": "<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>\n<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>\n<h2 id=\"configurer-netlify\">Configurer Netlify</h2>\n<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>\n<figure>\n<picture title=\"Ajout de site, étape 2 : choix du dépôt.\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Ajout de site, étape 2 : choix du dépôt.</figcaption>\n</figure>\n<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>\n<figure>\n<picture title=\"Ajout de site, étape 3 : configuration du déploiement.\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Ajout de site, étape 3 : configuration du déploiement.</figcaption>\n</figure>\n<h2 id=\"authentification-via-github\">Authentification via GitHub</h2>\n<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>\n<figure>\n<picture title=\"Configuration de l’application Oauth dans GitHub.\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Configuration de l’application Oauth dans GitHub.</figcaption>\n</figure>\n<h2 id=\"ajout-des-fichiers-de-l-admin\">Ajout des fichiers de l’admin</h2>\n<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>\n<p><script src=\"https://gist.github.com/DirtyF/efa029c00cccf7c45300d5f10b0afd7c.js\"></p>\n<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>\n<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>\n<figure>\n<picture title=\"Aperçu du workflow de publication de Netlify CMS.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Aperçu du workflow de publication de Netlify CMS.</figcaption>\n</figure>\n<p>Vous pouvez préciser le dossier dans lequel vous sauvegardez vos images, ici elles vont dans le dossier <code>assets/images/</code>.</p>\n<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>\n<figure>\n<picture title=\"Édition des champs personnalisés d’un article.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Édition des champs personnalisés d’un article.</figcaption>\n</figure>\n<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>\n<pre><code class=\"language-sh hljs bash\">git add admin\ngit commit -m <span class=\"hljs-string\">\"Admin de Netlify CMS\"</span>\ngit push</code></pre>\n<h2 id=\"acceder-a-l-administration\">Accéder à l’administration</h2>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"La liste des articles dans Netlify CMS.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>La liste des articles dans Netlify CMS.</figcaption>\n</figure>\n<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>\n<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>\n<figure>\n<picture title=\"Configuration de l’accès au site Netlify.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Configuration de l’accès au site Netlify.</figcaption>\n</figure>\n<h2 id=\"et-voila\">Et voilà !</h2>\n<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>\naccessible depuis GitHub pour chaque pull-request créée. Elle est pas belle la vie ?</p>\n<figure>\n<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.\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<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>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/03/17/smashing-mag-va-dix-fois-plus-vite/",
      "url": "https://jamstatic.fr/2017/03/17/smashing-mag-va-dix-fois-plus-vite/",
      "title": "Smashing Magazine est maintenant 10 fois plus rapide",
      "summary": "Sachant que leur site pouvait aller six fois plus vite en tirant parti d’un CDN global, Smashing a opté pour une architecture décentralisée et s'est adjoint les services de Netlify pour les aider à migrer.",
      "date_published": "2017-03-17T11:44:01+00:00","content_text": "La refonte de Smashing Magazine à l’aide d’un générateur de site\nstatique et d’APIs tierces comme Algolia constitue un petit évènement pour le\ndéveloppement Web moderne. Smashing est en effet un des sites les plus consultés\net le fait de faire évoluer leur stack technique, de se reposer sur des outils\nactuels montre à quel point cet écosystème est mature et robuste. C’est la\nsociété Netlify, basée à San Francisco et spécialisée dans l’hébergement de\nsites statiques, qui a accompagné Smashing dans cette aventure. En traduisant\nl’article paru sur leur blog,\nnous continuons à promouvoir ces solutions dans l’espoir que cela vous incite à\nadopter à votre tour ce type d’architectures dans certains de vos projets.\n\n\n\nÉcouter l’épopée de Smashing Magazine\nSmashing Magazine a toujours été une plateforme de confiance pour les\ndéveloppeurs. C’est un endroit où on peut trouver les meilleures pratiques liées\nau développement Web. Cela fait longtemps que nous leur faisons confiance,\ndepuis que Matt Biilmann, co-fondateur et CEO de Netlify, a commencé à utiliser\nSmashing Magazine pour apprendre à programmer par lui-même il y a quelques\nannées de cela.\nEn novembre 2015, Matt a écrit\n\"Pourquoi les générateurs de site web statique sont le prochain gros truc\",\nqui est devenu un des articles les plus lus sur Smashing Magazine. Dans cet\narticle, Netlify a mené des expériences intéressantes. Nous avons effectué une\ncomparaison pour voir à quel point Smashing Magazine serait plus rapide s'il\nétait hébergé sur Netlify, qui tire parti de la rapidité d’un CDN global… les\npremiers tests ont montré que ça allait 6 fois plus vite !\nEn avril 2016, Matt a fait une présentation lors de la SmashingConf à San\nFrancisco où il a parlé de la Jamstack, une\narchitecture de développement moderne pour le Web basée sur le Javascript côté\nclient, des APIs réutilisables et du Markup pré-généré.\nLa Jamstack intègre des pratiques qui la rendent idéale pour le développement\nWeb moderne :\n\nLes sites basés sur la Jamstack tirent partie de la puissance des CDN pour\nbénéficier d’une vitesse et d’une performance impossibles à battre.\nTout est versionné dans Git, pas besoin de bases de données à répliquer, pas\nd’installation compliquée.\nLe code est pré-généré avec la Jamstack, les régénérations sont automatisées,\nles modifications ne seront pas en production avant la prochaine régénération…\npour ne citer que quelques exemples.\n\nC’est une nouvelle manière de bâtir des sites et des applications qui offrent de\nmeilleures performances, une sécurité plus élevée, un moindre coût de\nredimensionnement et une meilleure expérience de développement.\nUne renaissance du Web moderne est en train d’avoir lieu et Smashing comptait\nbien en faire partie. Mais d’abord, ils voulaient l’implémenter pour eux-mêmes.\nEn faisant cela Smashing consolide un peu plus le fait \"qu'ils appliquent ce\nqu'ils recommandent\" et qu'ils font partie des pionniers du développement Web\nmoderne. Le fait d’avoir choisi la Jamstack et Netlify pour les aider à\nconcrétiser cette vision ne pouvait que nous réjouir.\nUn travail titanesque\nSmashing Magazine a décidé l’année dernière de redesigner leur site, car ils\nfaisaient face à pas mal de problèmes avec leur configuration précédente. Ils\nutilisaient différents outils et plate-formes pour tout gérer, des abonnements\nen passant par les contenus, ce qui pouvait s'avérer frustrant quand on ne\nsavait pas où aller pour faire quelque chose.\nWordPress leur a causé de forts maux de têtes et Smashing n'était plus satisfait\nde ce qu'il pouvait proposer. Même en utilisant la plupart des plugins de cache\ndisponibles, il était clair que WordPress ne fonctionnait pas comme il fallait\npuisqu'il y avait des problèmes avec chacun des plugins de cache.\n\n\nVitaly Friedman : nous avions des problèmes de cache avec chacun des plugins de cache WordPress existants.\n\nSi Netlify voulait relever le défi, il fallait bien comprendre les priorités\ncruciales pour Smashing :\n\nL'accès à une plate-forme unifiée — un endroit qui regroupe les différents\noutils techniques utilisés pour la gestion du site,\nLa liberté de produire un design qu'ils aiment sans avoir à subir les\ncontraintes imposées par WordPress et les autres outils,\nLe site le plus performant possible en se focalisant sur la fiabilité et\nla rapidité.\n\n…le tout en l’espace de quelques mois1.\nRien que ça ? Après quelques échanges et une réunion avec toute l’équipe,\nNetlify a décidé qu'ils ne pouvaient laisser passer une aussi belle occasion.\nSmashing magazine serait la parfaite étude de cas pour montrer que l’utilisation\nde la Jamstack constitue la manière de développer des sites.\n\n\nMathias 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.\n\nCela signifie que Smashing avait beaucoup de besoins. Il fallait créer un outil\nde build pour le magazine, un gestionnaire de contenus pour les milliers\nd’articles, un moteur de commentaires (avec plus de 200 000 commentaires\n!), une plate-forme de e-commerce pour les ventes, un service d’abonnement et un\ncompte utilisateur.\nNous ne sommes généralement pas pour réinventer la roue, nous sommes donc partis\nà la recherche d’outils actuels susceptibles de répondre aux solides besoins de\nSmashing. Malheureusement nous nous sommes vite aperçus qu'il allait falloir\nrepartir de zéro.\nAinsi naquirent nos rêves d’open source\nNous voulions fournir à Smashing tous les outils et les fonctionnalités dont il\navait besoin. Mais il était également important pour nous que nos efforts en\nvaillent la peine et puissent bénéficier plus largement à l’ensemble de la\ncommunauté. Nous avions l’occasion de montrer que la Jamstack représente le\nfutur du développement Web tout en construisant des outils qui allaient pouvoir\nêtre utilisés par la communauté grandissante qui gravite autour. C’est pour cela\nque tout ce que nous avons développé pour Smashing est open source.\nEn partant des besoins de Smashing, le début de notre périple technique a\nconsisté à développer les APIS open source suivantes :\n\nGoTell — une API et un outil de build pour la gestion d’un grand nombre de\ncommentaires,\nGoCommerce — une petite API en Go pour les sites e-commerce pour la\ngestion des commandes et des paiements,\nGoJoin — une API qui intègre les abonnements Stripe pour les Single Page\nApps et les sites,\nGoTrue — une petite API open-source écrite en Go qui peut agir comme une\nAPI de service indépendante pour la gestion des inscriptions et des\nauthentifications. Elle est basée sur OAuth2 et JWT et peut gérer les\ninscriptions, les connexions et les données personnelles des utilisateurs.\n\nVous pouvez retrouver toutes ces APIs sur notre\npage open source. Il y a tout un\nhistorique technique derrière ces décisions et nous dévoilerons plus de détails\nà ce propos dans un prochain article.\nNotre CMS open source\nUne des tâches les plus importantes liées au travail pour Smashing a été sans\nconteste le projet de gestionnaire de contenus open source\nNetlify CMS.\n\n\nUn CMS qui unifie le travail des auteurs, des éditeurs et des développeurs.\n\nLoin de ses cousins bouffis et monolithiques comme WordPress, nous voulions que\nle CMS de Netlify permette aux éditeurs de contenus de bénéficier du processus\nde versionnement basé sur Git. Qui plus est, Netlify CMS est compatible avec\ntoutes les plate-formes et fonctionne avec (presque) tous les générateurs de\nsite statique compatibles avec GitHub. C’est rapide, simple, souple et nous\nsommes très fiers du résultat.\nSpéciale dédicace à toutes les technologies utilisées :\nAlgolia, un service de recherche super rapide en\ntemps réel, Hugo, combiné à une gestion moderne des assets\navec Gulp et Webpack, le tout basé sur le modèle open-source\nVictor Hugo.\nPutain, on l’a fait !\nQuelques mois plus tard, nous y voici… Smashing a annoncé aujourd’hui la sortie\ndu site en version bêta et même s'il reste\nencore du travail à faire, nous pouvons célébrer quelques victoires :\nSmashingMagazine.com est aujourd’hui bien plus rapide, ils sont passés d’un\ntemps de début de chargement de 800ms à 80ms. Les visiteurs de Smashing vont\nmaintenant bénéficier d’une expérience plus fluide grâce à de meilleures\nintégrations, une vitesse accrue et une meilleure performance.\n\n\nLe temps de début de chargement est bien plus rapide qu'auparavant.\n\nSmashing possède désormais la plate-forme unifiée dont ils rêvaient. Ils gèrent\nmaintenant à partir d’un seul et même endroit tous les solides besoins de\nleur site. Avec Netlify, les projets open source et une manière plus efficace de\ngérer les contenus, fini d’hésiter pour savoir sur quel bouton appuyer ou quel\noutil utiliser pour réaliser une action particulière.\nIls ont maintenant pleinement le contrôle sur le design de leur choix et pour\nrésultat un joli site.\n…et la JAMStack a prouvé qu'elle est une figure de proue de la renaissance du\nWeb moderne et nous avons été capable d’achever tout ce travail tout en\nfournissant une boîte à outil open source pour la communauté.\n\n\n\n\nNdT. Le projet s'est étendu de juin 2016 à mars 2017.&#160;&#8617;\n\n\n",
      "content_html": "<aside class=\"note note-intro\"><p>La refonte de Smashing Magazine à l’aide d’un générateur de site\nstatique et d’APIs tierces comme Algolia constitue un petit évènement pour le\ndéveloppement Web moderne. Smashing est en effet un des sites les plus consultés\net le fait de faire évoluer leur stack technique, de se reposer sur des outils\nactuels montre à quel point cet écosystème est mature et robuste. C’est la\nsociété Netlify, basée à San Francisco et spécialisée dans l’hébergement de\nsites statiques, qui a accompagné Smashing dans cette aventure. En traduisant\n<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>,\nnous continuons à promouvoir ces solutions dans l’espoir que cela vous incite à\nadopter à votre tour ce type d’architectures dans certains de vos projets.</p></aside>\n<p><div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div></p>\n<h2 id=\"ecouter-l-epopee-de-smashing-magazine\">Écouter l’épopée de Smashing Magazine</h2>\n<p>Smashing Magazine a toujours été une plateforme de confiance pour les\ndéveloppeurs. C’est un endroit où on peut trouver les meilleures pratiques liées\nau développement Web. Cela fait longtemps que nous leur faisons confiance,\ndepuis que Matt Biilmann, co-fondateur et CEO de Netlify, a commencé à utiliser\nSmashing Magazine pour apprendre à programmer par lui-même il y a quelques\nannées de cela.</p>\n<p>En novembre 2015, Matt a écrit\n<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>,\nqui est devenu un des articles les plus lus sur Smashing Magazine. Dans cet\narticle, Netlify a mené des expériences intéressantes. Nous avons effectué une\ncomparaison pour voir à quel point Smashing Magazine serait plus rapide s'il\nétait hébergé sur Netlify, qui tire parti de la rapidité d’un CDN global… les\npremiers tests ont montré que ça allait <strong>6 fois plus vite</strong> !</p>\n<p>En avril 2016, Matt a fait une présentation lors de la SmashingConf à San\nFrancisco où il a parlé de la <a href=\"https://jamstack.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Jamstack</a>, une\narchitecture de développement moderne pour le Web basée sur le Javascript côté\nclient, des APIs réutilisables et du Markup pré-généré.</p>\n<p>La Jamstack intègre des pratiques qui la rendent idéale pour le développement\nWeb moderne :</p>\n<ul>\n<li>Les sites basés sur la Jamstack tirent partie de la puissance des CDN pour\nbénéficier d’une vitesse et d’une performance impossibles à battre.</li>\n<li>Tout est versionné dans Git, pas besoin de bases de données à répliquer, pas\nd’installation compliquée.</li>\n<li>Le code est pré-généré avec la Jamstack, les régénérations sont automatisées,\nles modifications ne seront pas en production avant la prochaine régénération…\npour ne citer que quelques exemples.</li>\n</ul>\n<p>C’est une nouvelle manière de bâtir des sites et des applications qui offrent de\nmeilleures performances, une sécurité plus élevée, un moindre coût de\nredimensionnement et une meilleure expérience de développement.</p>\n<p>Une renaissance du Web moderne est en train d’avoir lieu et Smashing comptait\nbien en faire partie. Mais d’abord, ils voulaient l’implémenter pour eux-mêmes.\nEn faisant cela Smashing consolide un peu plus le fait \"qu'ils appliquent ce\nqu'ils recommandent\" et qu'ils font partie des pionniers du développement Web\nmoderne. Le fait d’avoir choisi la Jamstack et Netlify pour les aider à\nconcrétiser cette vision ne pouvait que nous réjouir.</p>\n<h2 id=\"un-travail-titanesque\">Un travail titanesque</h2>\n<p>Smashing Magazine a décidé l’année dernière de redesigner leur site, car ils\nfaisaient face à pas mal de problèmes avec leur configuration précédente. Ils\nutilisaient différents outils et plate-formes pour tout gérer, des abonnements\nen passant par les contenus, ce qui pouvait s'avérer frustrant quand on ne\nsavait pas où aller pour faire quelque chose.</p>\n<p>WordPress leur a causé de forts maux de têtes et Smashing n'était plus satisfait\nde ce qu'il pouvait proposer. Même en utilisant la plupart des plugins de cache\ndisponibles, il était clair que WordPress ne fonctionnait pas comme il fallait\npuisqu'il y avait des problèmes avec <strong>chacun des plugins de cache</strong>.</p>\n<figure>\n<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=\"\">\n<figcaption>Vitaly Friedman : nous avions des problèmes de cache avec chacun des plugins de cache WordPress existants.</figcaption>\n</figure>\n<p><strong>Si</strong> Netlify voulait relever le défi, il fallait bien comprendre les priorités\ncruciales pour Smashing :</p>\n<ul>\n<li>L'accès à une <strong>plate-forme unifiée</strong> — un endroit qui regroupe les différents\noutils techniques utilisés pour la gestion du site,</li>\n<li>La liberté de produire <strong>un design</strong> qu'ils aiment sans avoir à subir les\ncontraintes imposées par WordPress et les autres outils,</li>\n<li>Le site le <strong>plus performant</strong> possible en se focalisant sur la fiabilité et\nla rapidité.</li>\n</ul>\n<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>\n<p>Rien que ça ? Après quelques échanges et une réunion avec toute l’équipe,\nNetlify a décidé qu'ils ne pouvaient laisser passer une aussi belle occasion.\nSmashing magazine serait la parfaite étude de cas pour montrer que l’utilisation\nde la Jamstack constitue <strong>la</strong> manière de développer des sites.</p>\n<figure>\n<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=\"\">\n<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>\n</figure>\n<p>Cela signifie que Smashing avait beaucoup de besoins. Il fallait créer un outil\nde build pour le magazine, un gestionnaire de contenus pour les milliers\nd’articles, un moteur de commentaires (avec plus de 200 000 commentaires\n!), une plate-forme de e-commerce pour les ventes, un service d’abonnement et un\ncompte utilisateur.</p>\n<p>Nous ne sommes généralement pas pour réinventer la roue, nous sommes donc partis\nà la recherche d’outils actuels susceptibles de répondre aux solides besoins de\nSmashing. Malheureusement nous nous sommes vite aperçus qu'il allait falloir\nrepartir de zéro.</p>\n<h2 id=\"ainsi-naquirent-nos-reves-d-open-source\">Ainsi naquirent nos rêves d’open source</h2>\n<p>Nous voulions fournir à Smashing tous les outils et les fonctionnalités dont il\navait besoin. Mais il était également important pour nous que nos efforts en\nvaillent la peine et puissent bénéficier plus largement à l’ensemble de la\ncommunauté. Nous avions l’occasion de montrer que la Jamstack représente le\nfutur du développement Web tout en construisant des outils qui allaient pouvoir\nêtre utilisés par la communauté grandissante qui gravite autour. C’est pour cela\nque tout ce que nous avons développé pour Smashing est <strong>open source</strong>.</p>\n<p>En partant des besoins de Smashing, le début de notre périple technique a\nconsisté à développer les APIS open source suivantes :</p>\n<ul>\n<li><strong>GoTell</strong> — une API et un outil de build pour la gestion d’un grand nombre de\ncommentaires,</li>\n<li><strong>GoCommerce</strong> — une petite API en Go pour les sites e-commerce pour la\ngestion des commandes et des paiements,</li>\n<li><strong>GoJoin</strong> — une API qui intègre les abonnements Stripe pour les Single Page\nApps et les sites,</li>\n<li><strong>GoTrue</strong> — une petite API open-source écrite en Go qui peut agir comme une\nAPI de service indépendante pour la gestion des inscriptions et des\nauthentifications. Elle est basée sur OAuth2 et JWT et peut gérer les\ninscriptions, les connexions et les données personnelles des utilisateurs.</li>\n</ul>\n<p>Vous pouvez retrouver toutes ces APIs sur notre\n<a href=\"https://www.netlify.com/open-source/\" target=\"_blank\" rel=\"noopener noreferrer\">page open source</a>. Il y a tout un\nhistorique technique derrière ces décisions et nous dévoilerons plus de détails\nà ce propos dans un prochain article.</p>\n<h2 id=\"notre-cms-open-source\">Notre CMS open source</h2>\n<p>Une des tâches les plus importantes liées au travail pour Smashing a été sans\nconteste le projet de gestionnaire de contenus open source\n<strong><a href=\"https://www.netlifycms.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Netlify CMS</a></strong>.</p>\n<figure>\n<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\">\n<figcaption>Un CMS qui unifie le travail des auteurs, des éditeurs et des développeurs.</figcaption>\n</figure>\n<p>Loin de ses cousins bouffis et monolithiques comme WordPress, nous voulions que\nle CMS de Netlify permette aux éditeurs de contenus de bénéficier du processus\nde versionnement basé sur Git. Qui plus est, Netlify CMS est compatible avec\ntoutes les plate-formes et fonctionne avec (presque) tous les générateurs de\nsite statique compatibles avec GitHub. C’est rapide, simple, souple et nous\nsommes très fiers du résultat.</p>\n<p><em>Spéciale dédicace à toutes les technologies utilisées</em> :</p>\n<p><a href=\"https://www.algolia.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Algolia</a>, un service de recherche super rapide en\ntemps réel, <a href=\"https://gohugo.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Hugo</a>, combiné à une gestion moderne des assets\navec Gulp et Webpack, le tout basé sur le modèle open-source\n<a href=\"https://github.com/netlify/victor-hugo\" target=\"_blank\" rel=\"noopener noreferrer\">Victor Hugo</a>.</p>\n<h2 id=\"putain-on-l-a-fait\">Putain, on l’a fait !</h2>\n<p>Quelques mois plus tard, nous y voici… Smashing a annoncé aujourd’hui la sortie\ndu <a href=\"https://next.smashingmagazine.com/\" target=\"_blank\" rel=\"noopener noreferrer\">site en version bêta</a> et même s'il reste\nencore du travail à faire, nous pouvons célébrer quelques victoires :</p>\n<p>SmashingMagazine.com est aujourd’hui bien plus rapide, ils sont passés d’un\ntemps de début de chargement de 800ms à 80ms. Les visiteurs de Smashing vont\nmaintenant bénéficier d’une expérience plus fluide grâce à de meilleures\nintégrations, une vitesse accrue et une meilleure performance.</p>\n<figure>\n<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=\"\">\n<figcaption>Le temps de début de chargement est bien plus rapide qu'auparavant.</figcaption>\n</figure>\n<p>Smashing possède désormais la plate-forme unifiée dont ils rêvaient. Ils gèrent\nmaintenant à partir <strong>d’un seul et même endroit</strong> tous les solides besoins de\nleur site. Avec Netlify, les projets open source et une manière plus efficace de\ngérer les contenus, fini d’hésiter pour savoir sur quel bouton appuyer ou quel\noutil utiliser pour réaliser une action particulière.</p>\n<p>Ils ont maintenant pleinement le contrôle sur le design de leur choix et pour\nrésultat <a href=\"https://next.smashingmagazine.com/\" target=\"_blank\" rel=\"noopener noreferrer\">un joli site</a>.</p>\n<p>…et la JAMStack a prouvé qu'elle est une figure de proue de la renaissance du\nWeb moderne et nous avons été capable d’achever tout ce travail tout en\nfournissant une boîte à outil open source pour la communauté.</p>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/03/16/5-raisons-de-tester-la-jamstack/",
      "url": "https://jamstatic.fr/2017/03/16/5-raisons-de-tester-la-jamstack/",
      "title": "Passer au statique : 5 raisons de tester la Jamstack sur votre prochain projet",
      "summary": "Référencement, performance, sécurité, automatisation, communauté grandissante, les raisons de passer au statique ne manquent pas.",
      "date_published": "2017-03-16T00:00:00+00:00","content_text": "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.\nQue 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.\nAvant d’expliquer plus en détail pourquoi, commençons déjà par regarder de quoi on parle.\nJamstack et développement statique\nLe terme Jamstack désigne la stack JavaScript, APIs et Markup et une manière\nde construire des sites Web statiques sans base de données. Ce concept est\nvraiment tout bête — le mot \"statique\" sous-entend de la simplicité ou un\nmanque d’interactivité, mais c'est loin d’être le cas. Quand on parle de sites\nstatiques, on fait surtout référence aux technologies utilisées pour les\ngénérer, les mettre en ligne et les héberger.\nLe truc vraiment puissant c'est que le développement avec la Jamstack est bien\nplus simple à appréhender que le développement de sites dynamiques à base de\nCMS. Il est facile d’oublier le nombre d’étapes nécessaires pour satisfaire une\nsimple requête de page et la complexité des opérations qui sont menées\nconstamment côté serveur pour générer le HTML final affiché par le navigateur de\nl’utilisateur. Prenons l’exemple (très schématique) d’un site typique qui\nfonctionne sous WordPress.\n\n\n\n\n\n\nGénération de page HTML à l’aide de PHP et d’un serveur MySQL.\n\nQuand l’utilisateur demande à afficher une page, votre serveur fait une requête\ndans une base de données MySQL et utilise un interpréteur PHP, assemble les\ndonnées avec le thème et les plugins pour en faire un document HTML qui pourra\nensuite être affiché dans le navigateur de l’utilisateur. Ce qui fait que cette\nopération est extraordinairement complexe c'est surtout le templating. Vu\nqu'on ne va pas coder toutes les pages d’un blog, aussi modeste soit-il, à la\nmain : séparer les contenus en composants réutilisables et automatiser leur\nassemblage fait sens.\nMais pourquoi donc cette opération de templating a besoin de se passer côté\nserveur ? Avons-nous vraiment besoin de bases de données et de logiciels côté\nserveur (qui nous exposent par la même occasion à des douzaines de failles de\nsécurité) pour créer un simple blog ? Maintenant que les navigateurs sont\ndevenus des systèmes d’exploitation, capable d’interagir avec un nombre\nincalculable d’APIs et de faire tourner des applications complexes côté client,\net que le développement front-end est dominé par JavaScript et les\nautomatisations à l’aide de npm, n'avons-nous pas déjà dépassé ce modèle ?\n\n\n\n\n\n\nUn site Web statique développé à l’aide d’un processus basé sur la Jamstack.\n\nLes générateurs de site statique comme Jekyll et Hugo permettent de rendre cela\npossible. Ils nous servent essentiellement de système de templating à la place\ndu PHP mais au lieu de tourner sur un serveur et de générer du contenu à la\nvolée, ils tournent en local en tant que partie intégrante du processus de\ndéveloppement. Votre HTML est généré en amont et votre site Web — désormais un\nensemble de fichiers statiques faciles à mettre en cache — peut être distribué\npar un CDN (réseau de distribution de contenu) rapide comme l’éclair.\nMais leurs bienfaits ne s'arrêtent pas là.\n1. Référencement\nAssurément, je vais commencer avec le référencement. Le développement statique\nprésente une multitude de bénéfices pour la visibilité de votre site sur les\nmoteurs de recherche et tous ne sont pas forcément toujours appréciés à leur\njuste valeur.\nPremièrement, la simplification des URLs et de l’architecture du site est\nsouvent plus simple avec la Jamstack qu'avec un site dynamique et un CMS. Plutôt\nque de se reposer sur des règles de réécritures complexes d’URLs côté serveur\npour que votre contenu soit accessible via des URLS lisibles\n(example.com\/?p=12345 → example.com\/clair-et-net\/), vos URLs sont ce que\nvous voulez qu'elles soient : elles reflètent simplement l’emplacement des\nfichiers de votre site1.\nLe risque de duplication de contenus est également très réduit. Beaucoup de CMS\ngénèrent automatiquement des pages pour les catégories, les tags et les archives\npar date alors que vous n'en avez peut-être pas besoin. Généralement des\ndirectives noindex et des URLS canoniques sont ajoutées à l’aide de plugins\nsupplémentaires pour gérer tout ça. Les générateurs de site statique à l’inverse\nvous permettent de créer des pages finement et de mettre en place la taxonomie\nqui correspond à votre contenu. Si besoin, beaucoup de générateurs embarquent\ndes fonctions et une bonne logique pour créer, filtrer et paginer des pages\nd’archives.\nEnfin, il y a beaucoup d’avantages potentiels pour le SEO pour les sites qui\nutilisent beaucoup le rendu côté client et les frameworks JavaScript. Étant\ndonné le caractère statique de votre code source, le mécanisme qui consiste à\nservir des versions pré-rendues de votre HTML aux moteurs s'en retrouve\nnettement simplifié. Certains hébergeurs spécialisés dans les sites statiques\ncomme Netlify\noffrent même le pré-rendu de base\ngrâce à l’utilisation d’_escaped_fragment_, ça s'installe littéralement en\nun clic. Les personnes intéressées par ce sujet feraient bien d’aller lire\nl’étude de cas de Phil Hawksworth sur le\nrendu isomorphique avec les sites statiques.\n2. Performance\nLa performance est étroitement liée au référencement, car elle joue un rôle\nprépondérant en termes d’expérience utilisateur.\nLes avantages des sites statiques en termes de performance peuvent être\nphénoménaux. Avec la génération en amont du HTML et l’élimination des requêtes\nvers les bases de données, vos contenus peuvent être délivrés instantanément\ndepuis un CDN comme Amazon Cloudfront. Les tests effectués par Mathias Biilmann\navec\nSmashing Magazine\nont montré que même avec un site dynamique très optimisé (et une solide\nstratégie de cache), le temps de début de chargement était en moyenne six fois\nplus rapide avec une version statique distribuée via CDN. Smashing Magazine\nont d’ailleurs migré vers la Jamstack et Netlify à l’occasion de leur refonte.\nLa mise en cache s'en retrouve grandement simplifiée. Avec WordPress (ou\nn'importe quel CMS dynamique) les URLs peuvent retourner différents contenus en\nfonction des requêtes passées en paramètre et de facteurs comme si l’utilisateur\nà l’origine de la demande est authentifié ou non. Toute modification sur les\ntags, les catégories, les commentaires, les pages des auteurs, etc. peut avoir\nune incidence sur le fait qu'une page en cache est à jour ou pas. Avec un site\nWeb statique, toutes les URLs retournent le même fichier HTML à tous les\nutilisateurs et les mises à jour sont propagées dans le monde entier presque\ninstantanément. Tout contenu \"dynamique\" est alors géré côté client grâce à\nl’utilisation de JavaScript et d’APIs comme Disqus pour\nles commentaires ou FormKeep pour les formulaires.\n3. Sécurité\nOn va pouvoir passer rapidement sur ce point, car les sites Web statiques sont\nde véritables forteresses.\nSans bases de données, sans plugins, sans logiciel dynamique qui tourne sur\nvotre serveur, la possibilité d’injection de code et de hacks est fortement\nréduite. Quand votre site Web consiste en un ensemble de fichiers statiques,\ntoutes les fonctionnalités dynamiques sont alors prises en charge par les APIs\net le JavaScript côté client, on élimine ainsi le besoin de se reposer sur des\nplugins de CMS. Bien qu'il soit tout à fait possible qu'une API externe chargée\nde traiter des données persistantes expose une vulnérabilité, le fait d’éliminer\nvotre CMS entraîne la suppression de nombreux points de défaillance et de\nmultiples vecteurs d’attaque. Pour les blogs statiques, il n'est pas exagéré\nd’affirmer que la sécurité devient essentiellement un faux problème, du\nmoins comparé à une installation typique de WordPress.\nLes certificats SSL sont également faciles à installer et sont disponibles\ngratuitement grâce à des autorités de certification automatisées comme\nLetsEncrypt.\n4. Déploiement &amp; Workflow\nUne fois que vous avez travaillé sur un site Web avec la Jamstack — que vous\navez déployé des mises à jour et publié des contenus régulièrement — vous\ncommencez à ressentir le potentiel disruptif de cette manière de développer. Ça\névolue rapidement et on peut parfois se sentir un peu comme dans le Far West,\navec tous ces nouveaux outils et ces nouveaux services qui arrivent tous les\njours, mais ne vous y trompez pas : c'est puissant, flexible, on a atteint le\nstade de la maturité.\nUn des principes de base du développement avec la Jamstack c'est que tout vit\ndans un dépôt Git, que ce soit les composants de notre site statique, les\nfichiers de configuration de notre générateur, nos fichiers CSS et JS, nos\ncontenus écrits (sauvegardés sous forme de documents Markdown en texte brut).\nAvec votre service de déploiement et d’hébergement configuré pour refléter en\npermanence l’état de la branche de votre dépôt, appliquer une modification est\naussi simple que de pousser un commit sur un dépôt GitHub. L'ensemble de votre\nsite Web — le code et le contenu — vit dans un endroit centralisé, protégé par\nun versionnement robuste et peut être configuré pour être déployé en continu.\nOui mais et les clients dans tout ça ? Quid des utilisateurs non techniques\n? Les experts de la production de contenu qui sont à l’aise avec des éditeurs\ncomme celui de WordPress mais qui ne connaissent pas Markdown et GitHub ?\nLe problème a été identifié il y a maintenant plusieurs années et beaucoup de\nsolutions réjouissantes commencent à apparaître. Certaines sont\nextraordinairement simples. Si les éditeurs de vos contenus sont déjà familiers\navec\nles bases de Markdown -\nC’est-à-dire # titre, **gras**, *italique* — alors il n'y a aucune raison\nqu'ils ne puissent éditer le dépôt sous-jacent. Des outils comme\nProse.io s'intègrent à GitHub pour proposer une interface\nutilisateur plus adaptée pour les auteurs non techniques. Créez une branche pour\nles éditeurs de contenu puis fusionnez simplement leurs modifications pour\npublier de nouveaux contenus.\nSinon il y a aussi des dizaines de\nCMS \"headless\" qui sont\nparfaits pour les sites Web statiques. Ce sont avant tout des gestionnaires de\ncontenus reposant sur une API qui permettent de dissocier vos contenus du côté\nclient chargé de l’affichage de votre site.\nSiteleaf, par exemple qui fonctionne avec Jekyll et\nGitHub propose une édition dans le cloud compatible avec vos outils existants —\ncomme c'est écrit sur leur site les sites Web devraient survivre à leurs\nCMS.\nComme je le disais, c'est un secteur qui se développe rapidement, vous serez\nconfrontés à des challenges pour vous adapter à un workflow statique,\nparticulièrement pour les projets ambitieux. Quiconque s'intéresse à\nl’utilisation de sites statiques pour un projet commercial ou à grande échelle\ndevrait aller lire\nl’article de Stefan Baumgartner\nsur Smashing Magazine.\n5. Une communauté en pleine expansion\nLa popularité grandissante du développement de site statique a donné naissance à\nquelques nouveaux services assez incroyables.\nPrenez le e-commerce par exemple. Pour les petits vendeurs — ceux qui se\nreposeraient typiquement sur WordPress et WooCommerce — un site statique est\ndésormais une option parfaitement valable. Snipcart est\nun système de panier et de paiement basé sur JavaScript qui permet aux\ndéveloppeurs d’ajouter des fonctionnalités de e-commerce sur n'importe quel\nsite Web. L'inventaire des produits et des ventes est géré via le tableau de\nbord de Snipcart et son API permet l’intégration de systèmes de gestion\nd’inventaires, de livreurs, etc. Il existe d’autres solutions comme\nFoxy et le\nbouton d’achat Shopify.\nPour les blogueurs qui voudraient utiliser un thème tout fait pour leur\nsite, il en existe maintenant des centaines — allez faire un tour sur les\nannuaires de thèmes pour Hugo et\nHexo. Il convient de mentionner que certaines\nconnaissances techniques sont requises pour être opérationnel avec ces\ngénérateurs statiques.\nCes annuaires ne sont pas encore près de devenir des places de marché de thèmes\nprospères en tant que telles, mais en un sens c'est une bonne chose : les thèmes\nbourrés de plugins inutiles et d’outils de construction de pages ne sont pas un\nproblème ici !\nIl y a aussi des fournisseurs d’hébergement spécialisés dans les sites Web\nstatique. Netlify est le plus connu et à\njuste titre. Ils offrent un CDN mondial, des domaines personnalisés et des\ncertificats SSL gratuits ainsi qu'une intégration avec GitHub pour permettre de\ngénérer et de faire des déploiements atomiques depuis la ligne de commande. Le\nservice se vante aussi d’une bonne interface pour faire des choses gérées\ntypiquement sur votre serveur, comme les redirections, les pages d’erreur\npersonnalisées, la protection par mot de passe, la proxyfication, etc. (oui ça\nveut dire fini les htaccess).\nEst-ce que c'est fait pour moi ?\nBien entendu, il y a plein de sites Web pour lesquels le développement avec la\nJamstack n'est pas approprié. Il y a également des problèmes légitimes et\nquelques obstacles — auxquels vous ferez face même sur de tous petits projets —\nqui doivent être surmontés. Et plus particulièrement les services destinés aux\nutilisateurs non techniques et aux éditeurs de contenu doivent être encore plus\nsoignés. La vitesse à laquelle la plupart de ces outils évoluent peut s'avérer\ndéconcertante pour les nouveaux arrivants.\nToutefois je crois que l’écosystème autour du développement de site statique a\nmaintenant atteint un point critique. Dans bien des cas les avantages du\ndéveloppement statique surpassent maintenant les inconvénients. Une utilisation\nplus répandue de ces outils, de ces plate-formes et de ces services va les\npousser à s'étoffer.\nAlors est-ce que c'est fait pour vous ? Si vous êtes vaguement familier avec\nle développement Web et que vous n'avez pas encore testé un générateur de site\nstatique moderne, c'est le moment idéal pour le faire et de répondre vous-même à\ncette question. Prenez connaissance des ressources ci-dessous, regardez\n\n\n lors de la\nSmashingConf, puis essayez de remonter votre site perso à l’aide des outils de\ndéveloppement statique modernes, laissez tomber les base de données et passez\nsur un CDN rapide.\nVous aurez du mal à faire machine arrière et de nouvelles possibilités feront\nleur apparition un peu plus chaque jour.\nRessources\n\nCritiques de générateurs de site statique : Jekyll, Middleman, Hugo – Smashing Magazine\nUtilisation d’un générateur de site statique à grande échelle : leçons apprises – Smashing Magazine\nJamstack pour les clients : bénéfices, CMS pour site statique et limitations – Snipcart\nPassez au statique sans perdre votre serveur – Netlify\nC’est quoi un CMS headless ? – CSS-Tricks\nGestionnaires de contenus pour sites statiques – headlesscms.org\nJamstack | JavaScript, APIs et Markup – jamstack.org\nGénérateurs de site statique open-source – staticgen.com\n{static is} The New Dynamic – thenewdynamic.org\n\n\n\n\n\nNdT. Les URLs peuvent être également définies dans les métadonnées des fichiers de contenu.&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<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>\n<p>Avant d’expliquer plus en détail <em>pourquoi</em>, commençons déjà par regarder de <em>quoi</em> on parle.</p>\n<h2 id=\"jamstack-et-developpement-statique\">Jamstack et développement statique</h2>\n<p>Le terme <strong>Jamstack</strong> désigne la stack JavaScript, APIs et Markup et une manière\nde construire des sites Web statiques sans base de données. Ce concept est\n<em>vraiment tout bête</em> — le mot \"statique\" sous-entend de la simplicité ou un\nmanque d’interactivité, mais c'est loin d’être le cas. Quand on parle de sites\nstatiques, on fait surtout référence aux technologies utilisées pour les\ngénérer, les mettre en ligne et les héberger.</p>\n<p>Le truc vraiment puissant c'est que le développement avec la Jamstack est bien\nplus simple à appréhender que le développement de sites dynamiques à base de\nCMS. Il est facile d’oublier le nombre d’étapes nécessaires pour satisfaire une\nsimple requête de page et la complexité des opérations qui sont menées\nconstamment côté serveur pour générer le HTML final affiché par le navigateur de\nl’utilisateur. Prenons l’exemple (très schématique) d’un site typique qui\nfonctionne sous WordPress.</p>\n<figure>\n<picture title=\"Génération de page HTML à l’aide de PHP et d’un serveur MySQL.\">\n<source type=\"image/webp\" srcset=\"/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346597/diagram.27761d260df1135a6f7e5d65e9e85b97.webp\" width=\"740\" height=\"340\">\n<source type=\"image/avif\" srcset=\"/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346597/diagram.27761d260df1135a6f7e5d65e9e85b97.avif\" width=\"740\" height=\"340\">\n<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;\">\n</picture>\n<figcaption>Génération de page HTML à l’aide de PHP et d’un serveur MySQL.</figcaption>\n</figure>\n<p>Quand l’utilisateur demande à afficher une page, votre serveur fait une requête\ndans une base de données MySQL et utilise un interpréteur PHP, assemble les\ndonnées avec le thème et les plugins pour en faire un document HTML qui pourra\nensuite être affiché dans le navigateur de l’utilisateur. Ce qui fait que cette\nopération est extraordinairement complexe c'est surtout le <strong>templating</strong>. Vu\nqu'on ne va pas coder toutes les pages d’un blog, aussi modeste soit-il, à la\nmain : séparer les contenus en composants réutilisables et automatiser leur\nassemblage fait sens.</p>\n<p>Mais pourquoi donc cette opération de templating a besoin de se passer côté\nserveur ? Avons-nous vraiment besoin de bases de données et de logiciels côté\nserveur (qui nous exposent par la même occasion à des douzaines de failles de\nsécurité) pour créer un simple blog ? Maintenant que les navigateurs sont\ndevenus des systèmes d’exploitation, capable d’interagir avec un nombre\nincalculable d’APIs et de faire tourner des applications complexes côté client,\net que le développement front-end est dominé par JavaScript et les\nautomatisations à l’aide de <code>npm</code>, n'avons-nous pas déjà dépassé ce modèle ?</p>\n<figure>\n<picture title=\"Un site Web statique développé à l’aide d’un processus basé sur la Jamstack.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Un site Web statique développé à l’aide d’un processus basé sur la Jamstack.</figcaption>\n</figure>\n<p>Les générateurs de site statique comme Jekyll et Hugo permettent de rendre cela\npossible. Ils nous servent essentiellement de système de templating à la place\ndu PHP mais au lieu de tourner sur un serveur et de générer du contenu à la\nvolée, ils tournent en local en tant que partie intégrante du processus de\ndéveloppement. Votre HTML est généré en amont et votre site Web — désormais un\nensemble de fichiers statiques faciles à mettre en cache — peut être distribué\npar un CDN (réseau de distribution de contenu) rapide comme l’éclair.</p>\n<p>Mais leurs bienfaits ne s'arrêtent pas là.</p>\n<h2 id=\"1-referencement\">1. Référencement</h2>\n<p>Assurément, je vais commencer avec le référencement. Le développement statique\nprésente une multitude de bénéfices pour la visibilité de votre site sur les\nmoteurs de recherche et tous ne sont pas forcément toujours appréciés à leur\njuste valeur.</p>\n<p>Premièrement, la simplification des URLs et de l’architecture du site est\nsouvent plus simple avec la Jamstack qu'avec un site dynamique et un CMS. Plutôt\nque de se reposer sur des règles de réécritures complexes d’URLs côté serveur\npour que votre contenu soit accessible via des URLS lisibles\n(<code>example.com/?p=12345</code> → <code>example.com/clair-et-net/</code>), vos URLs sont ce que\nvous voulez qu'elles soient : elles reflètent simplement l’emplacement des\nfichiers de votre site<sup id=\"fnref1:1\"><a href=\"#fn:1\" class=\"footnote-ref\">1</a></sup>.</p>\n<p>Le risque de duplication de contenus est également très réduit. Beaucoup de CMS\ngénèrent automatiquement des pages pour les catégories, les tags et les archives\npar date alors que vous n'en avez peut-être pas besoin. Généralement des\ndirectives <code>noindex</code> et des URLS canoniques sont ajoutées à l’aide de plugins\nsupplémentaires pour gérer tout ça. Les générateurs de site statique à l’inverse\nvous permettent de créer des pages finement et de mettre en place la taxonomie\nqui correspond à <em>votre</em> contenu. Si besoin, beaucoup de générateurs embarquent\ndes fonctions et une bonne logique pour créer, filtrer et paginer des pages\nd’archives.</p>\n<p>Enfin, il y a beaucoup d’avantages potentiels pour le SEO pour les sites qui\nutilisent beaucoup le rendu côté client et les frameworks JavaScript. Étant\ndonné le caractère statique de votre code source, le mécanisme qui consiste à\nservir des versions pré-rendues de votre HTML aux moteurs s'en retrouve\nnettement simplifié. Certains hébergeurs spécialisés dans les sites statiques\ncomme Netlify\n<a href=\"https://www.netlify.com/docs/prerendering/\" target=\"_blank\" rel=\"noopener noreferrer\">offrent même le pré-rendu</a> de base\ngrâce à l’utilisation d’<code>_escaped_fragment_</code>, ça s'installe <em>littéralement</em> en\nun clic. Les personnes intéressées par ce sujet feraient bien d’aller lire\nl’étude de cas de Phil Hawksworth sur le\n<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>\n<h2 id=\"2-performance\">2. Performance</h2>\n<p>La performance est étroitement liée au référencement, car elle joue un rôle\nprépondérant en termes d’expérience utilisateur.</p>\n<p>Les avantages des sites statiques en termes de performance peuvent être\nphénoménaux. Avec la génération en amont du HTML et l’élimination des requêtes\nvers les bases de données, vos contenus peuvent être délivrés instantanément\ndepuis un CDN comme Amazon Cloudfront. Les tests effectués par Mathias Biilmann\navec\n<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>\nont montré que même avec un site dynamique très optimisé (et une solide\nstratégie de cache), le temps de début de chargement était en moyenne <strong>six fois\nplus rapide</strong> avec une version statique distribuée via CDN. Smashing Magazine\nont 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>\n<p>La mise en cache s'en retrouve grandement simplifiée. Avec WordPress (ou\nn'importe quel CMS dynamique) les URLs peuvent retourner différents contenus en\nfonction des requêtes passées en paramètre et de facteurs comme si l’utilisateur\nà l’origine de la demande est authentifié ou non. Toute modification sur les\ntags, les catégories, les commentaires, les pages des auteurs, etc. peut avoir\nune incidence sur le fait qu'une page en cache est à jour ou pas. Avec un site\nWeb statique, toutes les URLs retournent le <em>même</em> fichier HTML à tous les\nutilisateurs et les mises à jour sont propagées dans le monde entier presque\ninstantanément. Tout contenu \"dynamique\" est alors géré côté client grâce à\nl’utilisation de JavaScript et d’APIs comme <a href=\"https://disqus.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Disqus</a> pour\nles commentaires ou <a href=\"https://formkeep.com/\" target=\"_blank\" rel=\"noopener noreferrer\">FormKeep</a> pour les formulaires.</p>\n<h2 id=\"3-securite\">3. Sécurité</h2>\n<p>On va pouvoir passer rapidement sur ce point, car les sites Web statiques sont\nde véritables <strong>forteresses</strong>.</p>\n<p>Sans bases de données, sans plugins, sans logiciel dynamique qui tourne sur\nvotre serveur, la possibilité d’injection de code et de hacks est fortement\nréduite. Quand votre site Web consiste en un ensemble de fichiers statiques,\ntoutes les fonctionnalités dynamiques sont alors prises en charge par les APIs\net le JavaScript côté client, on élimine ainsi le besoin de se reposer sur des\nplugins de CMS. Bien qu'il soit tout à fait possible qu'une API externe chargée\nde traiter des données persistantes expose une vulnérabilité, le fait d’éliminer\nvotre CMS entraîne la suppression de nombreux points de défaillance et de\nmultiples vecteurs d’attaque. Pour les blogs statiques, il n'est pas exagéré\nd’affirmer que la sécurité devient essentiellement un <strong>faux problème</strong>, du\nmoins comparé à une installation typique de WordPress.</p>\n<p>Les certificats SSL sont également faciles à installer et sont disponibles\ngratuitement grâce à des autorités de certification automatisées comme\n<a href=\"https://letsencrypt.org/\" target=\"_blank\" rel=\"noopener noreferrer\">LetsEncrypt</a>.</p>\n<h2 id=\"4-deploiement-workflow\">4. Déploiement &amp; Workflow</h2>\n<p>Une fois que vous avez travaillé sur un site Web avec la Jamstack — que vous\navez déployé des mises à jour et publié des contenus régulièrement — vous\ncommencez à ressentir le potentiel disruptif de cette manière de développer. Ça\névolue rapidement et on peut parfois se sentir un peu comme dans le Far West,\navec tous ces nouveaux outils et ces nouveaux services qui arrivent tous les\njours, mais ne vous y trompez pas : c'est puissant, flexible, on a atteint le\nstade de la maturité.</p>\n<p>Un des principes de base du développement avec la Jamstack c'est que tout vit\ndans un dépôt Git, que ce soit les composants de notre site statique, les\nfichiers de configuration de notre générateur, nos fichiers CSS et JS, nos\ncontenus écrits (sauvegardés sous forme de documents Markdown en texte brut).\nAvec votre service de déploiement et d’hébergement configuré pour refléter en\npermanence l’état de la branche de votre dépôt, appliquer une modification est\naussi simple que de pousser un commit sur un dépôt GitHub. L'ensemble de votre\nsite Web — le code et le contenu — vit dans un endroit centralisé, protégé par\nun versionnement robuste et peut être configuré pour être déployé en continu.</p>\n<p><strong>Oui mais et les clients dans tout ça ?</strong> Quid des utilisateurs non techniques\n? Les experts de la production de contenu qui sont à l’aise avec des éditeurs\ncomme celui de WordPress mais qui ne connaissent pas Markdown et GitHub ?</p>\n<p>Le problème a été identifié il y a maintenant plusieurs années et beaucoup de\nsolutions réjouissantes commencent à apparaître. Certaines sont\nextraordinairement simples. Si les éditeurs de vos contenus sont déjà familiers\navec\n<a href=\"https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet\" target=\"_blank\" rel=\"noopener noreferrer\">les bases de Markdown</a> -\nC’est-à-dire <code># titre</code>, <code>**gras**</code>, <code>*italique*</code> — alors il n'y a aucune raison\nqu'ils ne puissent éditer le dépôt sous-jacent. Des outils comme\n<a href=\"https://prose.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Prose.io</a> s'intègrent à GitHub pour proposer une interface\nutilisateur plus adaptée pour les auteurs non techniques. Créez une branche pour\nles éditeurs de contenu puis fusionnez simplement leurs modifications pour\npublier de nouveaux contenus.</p>\n<p>Sinon il y a aussi des dizaines de\n<a href=\"https://css-tricks.com/what-is-a-headless-cms/\" target=\"_blank\" rel=\"noopener noreferrer\">CMS \"headless\"</a> qui sont\nparfaits pour les sites Web statiques. Ce sont avant tout des gestionnaires de\ncontenus reposant sur une API qui permettent de dissocier vos contenus du côté\nclient chargé de l’affichage de votre site.\n<a href=\"https://www.siteleaf.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Siteleaf</a>, par exemple qui fonctionne avec Jekyll et\nGitHub propose une édition dans le cloud compatible avec vos outils existants —\ncomme c'est écrit sur leur site <strong>les sites Web devraient survivre à leurs\nCMS</strong>.</p>\n<p>Comme je le disais, c'est un secteur qui se développe rapidement, vous <em>serez</em>\nconfrontés à des challenges pour vous adapter à un workflow statique,\nparticulièrement pour les projets ambitieux. Quiconque s'intéresse à\nl’utilisation de sites statiques pour un projet commercial ou à grande échelle\ndevrait aller lire\n<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>\nsur Smashing Magazine.</p>\n<h2 id=\"5-une-communaute-en-pleine-expansion\">5. Une communauté en pleine expansion</h2>\n<p>La popularité grandissante du développement de site statique a donné naissance à\nquelques nouveaux services assez incroyables.</p>\n<p>Prenez le e-commerce par exemple. Pour les petits vendeurs — ceux qui se\nreposeraient typiquement sur WordPress et WooCommerce — un site statique est\ndésormais une option parfaitement valable. <a href=\"https://snipcart.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Snipcart</a> est\nun système de panier et de paiement basé sur JavaScript qui permet aux\ndéveloppeurs d’ajouter des fonctionnalités de e-commerce sur n'importe quel\nsite Web. L'inventaire des produits et des ventes est géré via le tableau de\nbord de Snipcart et son API permet l’intégration de systèmes de gestion\nd’inventaires, de livreurs, etc. Il existe d’autres solutions comme\n<a href=\"https://www.foxy.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Foxy</a> et le\n<a href=\"https://www.shopify.co.uk/buy-button\" target=\"_blank\" rel=\"noopener noreferrer\">bouton d’achat Shopify</a>.</p>\n<p>Pour les <strong>blogueurs</strong> qui voudraient utiliser un thème tout fait pour leur\nsite, il en existe maintenant des centaines — allez faire un tour sur les\nannuaires de thèmes pour <a href=\"https://themes.gohugo.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Hugo</a> et\n<a href=\"https://hexo.io/themes/\" target=\"_blank\" rel=\"noopener noreferrer\">Hexo</a>. Il convient de mentionner que certaines\nconnaissances techniques sont requises pour être opérationnel avec ces\ngénérateurs statiques.</p>\n<p>Ces annuaires ne sont pas encore près de devenir des places de marché de thèmes\nprospères en tant que telles, mais en un sens c'est une bonne chose : les thèmes\nbourrés de plugins inutiles et d’outils de construction de pages ne sont pas un\nproblème ici !</p>\n<p>Il y a aussi des <strong>fournisseurs d’hébergement</strong> spécialisés dans les sites Web\nstatique. <a href=\"https://www.netlify.com/features/\" target=\"_blank\" rel=\"noopener noreferrer\">Netlify</a> est le plus connu et à\njuste titre. Ils offrent un CDN mondial, des domaines personnalisés et des\ncertificats SSL gratuits ainsi qu'une intégration avec GitHub pour permettre de\ngénérer et de faire des déploiements atomiques depuis la ligne de commande. Le\nservice se vante aussi d’une bonne interface pour faire des choses gérées\ntypiquement sur votre serveur, comme les redirections, les pages d’erreur\npersonnalisées, la protection par mot de passe, la proxyfication, etc. (oui ça\nveut dire <strong>fini les htaccess</strong>).</p>\n<h2 id=\"est-ce-que-c-est-fait-pour-moi\">Est-ce que c'est fait pour moi ?</h2>\n<p>Bien entendu, il y a plein de sites Web pour lesquels le développement avec la\nJamstack n'est pas approprié. Il y a également des problèmes légitimes et\nquelques obstacles — auxquels vous ferez face même sur de tous petits projets —\nqui doivent être surmontés. Et plus particulièrement les services destinés aux\nutilisateurs non techniques et aux éditeurs de contenu doivent être encore plus\nsoignés. La vitesse à laquelle la plupart de ces outils évoluent peut s'avérer\ndéconcertante pour les nouveaux arrivants.</p>\n<p>Toutefois je crois que l’écosystème autour du développement de site statique a\nmaintenant atteint un <strong>point critique</strong>. Dans bien des cas les avantages du\ndéveloppement statique surpassent maintenant les inconvénients. Une utilisation\nplus répandue de ces outils, de ces plate-formes et de ces services va les\npousser à s'étoffer.</p>\n<p><strong>Alors est-ce que c'est fait pour vous ?</strong> Si vous êtes vaguement familier avec\nle développement Web et que vous n'avez pas encore testé un générateur de site\nstatique moderne, c'est le moment idéal pour le faire et de répondre vous-même à\ncette question. Prenez connaissance des ressources ci-dessous, regardez\n<div style=\"position:relative;padding-bottom:56.25%;height:0;overflow:hidden;\">\n<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>\n</div> lors de la\nSmashingConf, puis essayez de remonter votre site perso à l’aide des outils de\ndéveloppement statique modernes, laissez tomber les base de données et passez\nsur un CDN rapide.</p>\n<p>Vous aurez du mal à faire machine arrière et de nouvelles possibilités feront\nleur apparition un peu plus chaque jour.</p>\n<h3 id=\"ressources\">Ressources</h3>\n<ul>\n<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>\n<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>\n<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>\n<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>\n<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>\n<li>Gestionnaires de contenus pour sites statiques – <a href=\"https://headlesscms.org/\" target=\"_blank\" rel=\"noopener noreferrer\">headlesscms.org</a></li>\n<li>Jamstack | JavaScript, APIs et Markup – <a href=\"https://jamstack.org/\" target=\"_blank\" rel=\"noopener noreferrer\">jamstack.org</a></li>\n<li>Générateurs de site statique open-source – <a href=\"https://www.staticgen.com/\" target=\"_blank\" rel=\"noopener noreferrer\">staticgen.com</a></li>\n<li>{static is} The New Dynamic – <a href=\"https://www.thenewdynamic.org/\" target=\"_blank\" rel=\"noopener noreferrer\">thenewdynamic.org</a></li>\n</ul>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/03/11/mode-offline-avec-hugo/",
      "url": "https://jamstatic.fr/2017/03/11/mode-offline-avec-hugo/",
      "title": "Passez en mode hors-connexion avec un Service Worker et Hugo !",
      "summary": "Permettez aux visiteurs de consulter votre site statique en mode hors-connexion à l’aide d’un Service Worker",
      "date_published": "2017-03-11T15:04:00+00:00","content_text": "La majorité des articles publiés jusqu'ici se référaient à Jekyll,\ncette fois place à Hugo. Hugo est un générateur de site\nstatique populaire très performant et beaucoup plus performant pour vos\nvisiteurs si vous lui adjoignez les services d’un Service Worker pour gérer le\nmode déconnecté de votre site web. Notez que les explications fournies ici sont\nvalables et facilement adaptables pour tout autre générateur statique.\nAprès le mobile first, place maintenant au offline first et\naux progressive web apps (PWA)\ntous deux très tendances en ce moment. Les Service Workers jouent un rôle majeur\ndans tous les cas de figure. Un Service Worker en gros c'est un script qui va\njouer le rôle d’un proxy entre le navigateur web et le réseau Internet. Vous\ntrouverez dans cet article un exemple simple qui vous permettra d’installer un\nService Worker sur un site statique généré avec Hugo afin\nde le rendre ultra-performant.\nDe quoi parle-t-on ?\nSi vous n'avez pas encore entendu parler des Service Workers et que vous voulez\nen savoir plus sur le sujet, merci de consulter les liens suivants :\n\nVotre première Progressive Web App publié sur Google Developers\nL'API Service Worker publié sur MDN Mozilla Developer Network\nService Worker Revolution publié chez Ponyfoo\nTout ce que vous devez savoir pour créer vos premières applications hors-ligne sur Github\n\nMaintenant que vous avez lu tout ça — ou du moins que vous avez compris de quoi\nil en retourne — voici ce que nous allons faire :\n\nInstaller un Service Worker à partir d’un exemple dans Hugo.\nAfficher une page hors-connexion personnalisée en cas de panne de réseau ou si la page n'est pas en cache\nAfficher une page d’erreur 404 personnalisée en cas de requêtes HHTP retournant une erreur client de type 4xx\nAjouter un fichier manifest.json pour définir l’apparence de l’application Web sur mobile.\n\nPrérequis\nCréer une page hors-connexion\nAssurez-vous de créer une page hors-connexion personnalisée pour afficher à vos\nvisiteurs quand ils sont déconnectés du réseau.\nPar exemple vous pouvez créer les fichiers suivants :\n├── content\n│   ├── offline.md\n├── layouts\n│   ├── offline\/single.html\nContenu du fichier content\/offline.md :\n+++\ndate = \"2016-10-16T19:28:41+02:00\"\ndraft = false\ntitle = \"Oops, vous êtes déconnecté du réseau.\"\ntype = \"offline\"\n+++\n\nEssayez de vous connecter à Internet pour naviguer sur le site.\nLe fichier layouts\/offline\/single.html :\n&lt;html&gt;\n &lt;head&gt;\n  &lt;title&gt;{{ .Title }}&lt;\/title&gt;\n &lt;\/head&gt;\n &lt;body&gt;\n    &lt;h1&gt;{{ .Title }}&lt;\/h1&gt;\n    {{ .Content }}\n &lt;\/body&gt;\n&lt;\/html&gt;\nC’est vraiment un exemple minimaliste, vous pouvez bien entendu créer une page\nhors-connexion avec le contenu de votre choix.\nMais déjà grâce à notre exemple, nous avons généré une page\noffline\/index.html. OK, ça c'est fait.\nCréer une page 404 personnalisée\nSi votre projet ne possède pas encore de page 404 personnalisée, vous pouvez\nvous référer à\nla documentation d’Hugo pour créer une page 404\nou vous contenter de suivre les quelques instructions de base ci-dessous.\nPour cela, vous aurez besoin des fichiers suivants :\n├──content\n│   ├── 404.md\n├── layouts\n│   ├── 404.html\nLe fichier content\/404.md :\n+++\ndate = \"2016-10-16T19:28:41+02:00\"\ndraft = false\ntitle = \"Zut… Page non trouvée.\"\n+++\n\nVous devriez aller voir ailleurs.\nLe fichier layouts\/404.html :\n&lt;html&gt;\n  &lt;head&gt;\n    &lt;title&gt;{{ .Title }}&lt;\/title&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;h1&gt;{{ .Title }}&lt;\/h1&gt;\n    {{ .Content }}\n  &lt;\/body&gt;\n&lt;\/html&gt;\nCréer les icônes de l’application Web\nLes icônes des applications sont juste des favicons qu'on affiche sur un écran\nde démarrage au chargement du site depuis l’écran d’accueil.\nLes tailles suivantes sont recommandées :\n\n128px × 128px\n144px × 144px\n152px × 152px\n192px × 192px\n256px × 256px\n\nPour les générer rapidement, vous pouvez utiliser un service comme\nfavicomatic.com.\nEnsuite placez les fichiers PNG dans votre dossier \/static folder. Par\nexemple :\n├── static\n│   ├── favicons\n│   │   ├── icon-128x128.png\n│   │   ├── icon-144x144.png\n│   │   ├── icon-152x152.png\n│   │   ├── icon-192x192.png\n│   │   ├── icon-256x256.png\nInstallation du fichier manifest.json\nLe vrai travail commence maintenant avec la création et la configuration du\nfichier manifest.json.\nNous allons utiliser pour cela un\nexemple de fichier manifest\nexistant tiré du dépôt offline-first-sw.\nPlacez ce fichier également dans le dossier static\/, il doit obligatoirement\nse trouver à la racine comme ceci :\n├── static\n│   ├── manifest.json\nVous pouvez recopier ce fichier à la main ou utiliser la commande suivante si\nvous travaillez dans un environnement GNU Linux ou macOS :\n# à partir du dossier racine d’Hugo\ncd static\nwget https:\/\/raw.githubusercontent.com\/wildhaber\/offline-first-sw\/master\/manifest.js\nVous devriez maintenant avoir un fichier qui ressemble à cela dans votre dossier\nstatic :\n{\n  \"name\": \"&lt;nom-de-votre-application&gt;\",\n  \"short_name\": \"&lt;nom-abrégé&gt;\",\n  \"icons\": [\n    {\n      \"src\": \"\/img\/icons\/logo-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image\/png\"\n    },\n    {\n      \"src\": \"\/img\/icons\/logo-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image\/png\"\n    },\n    {\n      \"src\": \"\/img\/icons\/logo-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image\/png\"\n    },\n    {\n      \"src\": \"\/img\/icons\/logo-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image\/png\"\n    },\n    {\n      \"src\": \"\/img\/icons\/logo-256x256.png\",\n      \"sizes\": \"256x256\",\n      \"type\": \"image\/png\"\n    }\n  ],\n  \"start_url\": \"\/index.html\",\n  \"display\": \"standalone\",\n  \"orientation\": \"portrait\",\n  \"background_color\": \"#000000\",\n  \"theme_color\": \"#000000\"\n}\nAjustez les valeurs à votre guise.\nAjoutez un lien vers manifest.json dans votre modèle\nPour que le navigateur soit en mesure de détecter votre manifest.json, ajoutez\nle bout du code suivant dans le &lt;head&gt; de vos modèles :\n&lt;link rel=\"manifest\" href=\"\/manifest.json\" \/&gt;\nInstallation du Service Worker\nPour cela nous allons aussi utiliser l’exemple de\nService Worker\nfourni dans le dépôt\noffline-first-sw.\nLe fichier sw.js doit également se trouver à la racine du dossier static\ncomme ceci :\n├── static\n│   ├── sw.js\nLà encore soit vous recopiez le fichier à la main, soit vous utilisez la\ncommande suivante dans un environnement GNU Linux ou macOS :\n# à partir du dossier racine d’Hugo\ncd static\nwget https:\/\/raw.githubusercontent.com\/wildhaber\/offline-first-sw\/master\/sw.js\nVous devez vous retrouver avec le fichier suivant à la racine :\nconst CACHE_VERSION = 1;\n\nconst BASE_CACHE_FILES = [\n  '\/style.css',\n  '\/script.js',\n  '\/search.json',\n  '\/manifest.json',\n  '\/favicon.png',\n];\n\nconst OFFLINE_CACHE_FILES = [\n  '\/style.css',\n  '\/script.js',\n  '\/offline\/index.html',\n];\n\nconst NOT_FOUND_CACHE_FILES = [\n  '\/style.css',\n  '\/script.js',\n  '\/404.html',\n];\n\nconst OFFLINE_PAGE = '\/offline\/index.html';\nconst NOT_FOUND_PAGE = '\/404.html';\n\nconst CACHE_VERSIONS = {\n  assets: 'assets-v' + CACHE_VERSION,\n  content: 'content-v' + CACHE_VERSION,\n  offline: 'offline-v' + CACHE_VERSION,\n  notFound: '404-v' + CACHE_VERSION,\n};\n\n\/\/ Durée de mise en cache en SECONDES en fonction des différentes extensions\nconst MAX_TTL = {\n  '\/': 3600,\n  html: 3600,\n  json: 86400,\n  js: 86400,\n  css: 86400,\n};\n\nconst CACHE_BLACKLIST = [\n  \/\/(str) =&gt; {\n  \/\/    return !str.startsWith('http:\/\/localhost') &amp;&amp; !str.startsWith('https:\/\/jamstatic.fr');\n  \/\/},\n];\n\nconst SUPPORTED_METHODS = [\n  'GET',\n];\n\n\/**\n * isBlackListed\n * @param {string} url\n * @returns {boolean}\n *\/\nfunction isBlacklisted(url) {\n  return (CACHE_BLACKLIST.length &gt; 0) ? !CACHE_BLACKLIST.filter((rule) =&gt; {\n    if (typeof rule === 'function') {\n      return !rule(url);\n    } else {\n      return false;\n    }\n  }).length : false\n}\n\n\/**\n * getFileExtension\n * @param {string} url\n * @returns {string}\n *\/\nfunction getFileExtension(url) {\n  let extension = url.split('.').reverse()[0].split('?')[0];\n  return (extension.endsWith('\/')) ? '\/' : extension;\n}\n\n\/**\n * getTTL\n * @param {string} url\n *\/\nfunction getTTL(url) {\n  if (typeof url === 'string') {\n    let extension = getFileExtension(url);\n    if (typeof MAX_TTL[extension] === 'number') {\n      return MAX_TTL[extension];\n    } else {\n      return null;\n    }\n  } else {\n    return null;\n  }\n}\n\n\/**\n * installServiceWorker\n * @returns {Promise}\n *\/\nfunction installServiceWorker() {\n  return Promise.all(\n    [\n      caches.open(CACHE_VERSIONS.assets)\n      .then(\n        (cache) =&gt; {\n          return cache.addAll(BASE_CACHE_FILES);\n        }\n      ),\n      caches.open(CACHE_VERSIONS.offline)\n      .then(\n        (cache) =&gt; {\n          return cache.addAll(OFFLINE_CACHE_FILES);\n        }\n      ),\n      caches.open(CACHE_VERSIONS.notFound)\n      .then(\n        (cache) =&gt; {\n          return cache.addAll(NOT_FOUND_CACHE_FILES);\n        }\n      )\n    ]\n  );\n}\n\n\/**\n * cleanupLegacyCache\n * @returns {Promise}\n *\/\nfunction cleanupLegacyCache() {\n\n  let currentCaches = Object.keys(CACHE_VERSIONS)\n    .map(\n      (key) =&gt; {\n        return CACHE_VERSIONS[key];\n      }\n    );\n\n  return new Promise(\n    (resolve, reject) =&gt; {\n\n      caches.keys()\n        .then(\n          (keys) =&gt; {\n            return legacyKeys = keys.filter(\n              (key) =&gt; {\n                return !~currentCaches.indexOf(key);\n              }\n            );\n          }\n        )\n        .then(\n          (legacy) =&gt; {\n            if (legacy.length) {\n              Promise.all(\n                  legacy.map(\n                    (legacyKey) =&gt; {\n                      return caches.delete(legacyKey)\n                    }\n                  )\n                )\n                .then(\n                  () =&gt; {\n                    resolve()\n                  }\n                )\n                .catch(\n                  (err) =&gt; {\n                    reject(err);\n                  }\n                );\n            } else {\n              resolve();\n            }\n          }\n        )\n        .catch(\n          () =&gt; {\n            reject();\n          }\n        );\n\n    }\n  );\n}\n\n\nself.addEventListener(\n  'install', event =&gt; {\n    event.waitUntil(installServiceWorker());\n  }\n);\n\n\/\/ La méthode activate est chargée de supprimer les vieux caches\nself.addEventListener(\n  'activate', event =&gt; {\n    event.waitUntil(\n      Promise.all(\n        [\n          cleanupLegacyCache(),\n        ]\n      )\n      .catch(\n        (err) =&gt; {\n          event.skipWaiting();\n        }\n      )\n    );\n  }\n);\n\nself.addEventListener(\n  'fetch', event =&gt; {\n\n    event.respondWith(\n      caches.open(CACHE_VERSIONS.content)\n      .then(\n        (cache) =&gt; {\n\n          return cache.match(event.request)\n            .then(\n              (response) =&gt; {\n\n                if (response) {\n\n                  let headers = response.headers.entries();\n                  let date = null;\n\n                  for (let pair of headers) {\n                    if (pair[0] === 'date') {\n                      date = new Date(pair[1]);\n                    }\n                  }\n\n                  if (date) {\n                    let age = parseInt((new Date().getTime() - date.getTime()) \/ 1000);\n                    let ttl = getTTL(event.request.url);\n\n                    if (ttl &amp; amp; &amp; amp; age &gt; ttl) {\n\n                      return new Promise(\n                          (resolve) =&gt; {\n\n                            return fetch(event.request)\n                              .then(\n                                (updatedResponse) =&gt; {\n                                  if (updatedResponse) {\n                                    cache.put(event.request, updatedResponse.clone());\n                                    resolve(updatedResponse);\n                                  } else {\n                                    resolve(response)\n                                  }\n                                }\n                              )\n                              .catch(\n                                () =&gt; {\n                                  resolve(response);\n                                }\n                              );\n\n                          }\n                        )\n                        .catch(\n                          (err) =&gt; {\n                            return response;\n                          }\n                        );\n                    } else {\n                      return response;\n                    }\n\n                  } else {\n                    return response;\n                  }\n\n                } else {\n                  return null;\n                }\n              }\n            )\n            .then(\n              (response) =&gt; {\n                if (response) {\n                  return response;\n                } else {\n                  return fetch(event.request)\n                    .then(\n                      (response) =&gt; {\n\n                        if (response.status &lt; 400) {\n                          if (~SUPPORTED_METHODS.indexOf(event.request.method) &amp; amp; &amp; amp; !isBlacklisted(event.request.url)) {\n                            cache.put(event.request, response.clone());\n                          }\n                          return response;\n                        } else {\n                          return caches.open(CACHE_VERSIONS.notFound).then((cache) =&gt; {\n                            return cache.match(NOT_FOUND_PAGE);\n                          })\n                        }\n                      }\n                    )\n                    .then((response) =&gt; {\n                      if (response) {\n                        return response;\n                      }\n                    })\n                    .catch(\n                      () =&gt; {\n\n                        return caches.open(CACHE_VERSIONS.offline)\n                          .then(\n                            (offlineCache) =&gt; {\n                              return offlineCache.match(OFFLINE_PAGE)\n                            }\n                          )\n\n                      }\n                    );\n                }\n              }\n            )\n            .catch(\n              (error) =&gt; {\n                console.error('Error in fetch handler:', error);\n                throw error;\n              }\n            );\n        }\n      )\n    );\n\n  }\n);\nMaintenant vous pouvez définir le comportement souhaité pour votre Service\nWorker :\nFichiers à mettre en cache par défaut\nconst BASE_CACHE_FILES = [\n  \"\/style.css\",\n  \"\/script.js\",\n  \"\/search.json\",\n  \"\/manifest.json\",\n  \"\/favicon.png\",\n];\nListez dans ce tableau tous les fichiers qui devraient être mis en cache par\ndéfaut\nFichiers en mode hors-connexion\nconst OFFLINE_CACHE_FILES = [\"\/style.css\", \"\/script.js\", \"\/offline\/index.html\"];\nListez dans ce tableau les fichiers nécessaires pour l’affichage de votre page\noffline.\nFichiers en cas d’erreur 4xx\nconst NOT_FOUND_CACHE_FILES = [\"\/style.css\", \"\/script.js\", \"\/404.html\"];\nListez dans ce tableau les fichiers nécessaires pour l’affichage de votre page\nd’erreur 404.\nPage hors-connexion\nconst OFFLINE_PAGE = \"\/offline\/index.html\";\nC’est la page qui sera affichée quand le visiteur sera déconnecté du réseau ou\nque la page n'est pas déjà en cache.\nPage d’erreur\nconst NOT_FOUND_PAGE = \"\/404.html\";\nLe chemin de la page qui sera affichée en cas d’erreur de type 4xx.\nDurée de mise en cache\nconst MAX_TTL = {\n  \"\/\": 3600,\n  html: 3600,\n  json: 86400,\n  js: 86400,\n  css: 86400,\n};\nCe tableau clé-valeur indique pour chaque type d’extension de fichier la durée\nmaximum de mise en cache appelée Time To Live (définit en secondes et pas\nen millisecondes). C’est le temps qui s'écoulera avant qu'un fichier ne soit mis\nà jour à partir du réseau.\nLes extensions non présentes resteront en cache jusqu'à la prochaine la mise à\njour du cache par le Service Worker.\n\/\/ 60 = 1 minute\n\/\/ 3600 = 1 heure\n\/\/ 86400 = 1 jour\n\/\/ 604800 = 1 semaine\n\/\/ 2592000 = 30 jours (~ 1 mois)\n\/\/ 31536000 = 1 an\nFichiers à exclure de la mise en cache\nconst CACHE_BLACKLIST = [\n  (str) =&gt; {\n    \/\/ str = URL de la ressource\n    \/\/ Appliquez cette règle lorsque vous ne voulez pas mettre des fichiers externes en cache\n    return !str.startsWith(\"https:\/\/votresiteweb.tld\");\n  },\n];\nAjustez ces paramètres au contexte de votre site ou de votre application.\nEnregistrement du Service Worker\nAjoutez le script suivant avant la fermeture de la balise &lt;body&gt; ou placez-le\ndans votre fichier JavaScript généré :\n&lt;script&gt;\n  if (\"serviceWorker\" in navigator) {\n    navigator.serviceWorker\n      .register(\"\/sw.js\", { scope: \"\/\" })\n      .then(function (registration) {\n        console.log(\"Service Worker enregistré\");\n      });\n\n    navigator.serviceWorker.ready.then(function (registration) {\n      console.log(\"Service Worker prêt\");\n    });\n  }\n&lt;\/script&gt;\nCe code JS va enregistrer, installer et activer votre Service Worker.\nVous en avez à présent terminé avec toutes les étapes nécessaires. Vous disposez\nmaintenant d’un site Hugo ultra-rapide. :)\nDéboguer votre Service Worker\nPour déboguer un Service Worker avec Google Chrome, il vous suffit d’ouvrir la\nconsole et d’aller dans l’onglet Application. C’est là que vous trouverez\nvotre Service Worker et vos caches.\nVous en apprendrez davantage sur le\ndébogage de Service Workers\nsur le site pour les développeurs de Google.\nSi votre navigateur préféré est Firefox vous en saurez plus sur\nle débogage des Service Workers et Push à l’aide des outils de développement pour Firefox\nsur hacks.mozilla.org.",
      "content_html": "<aside class=\"note note-intro\"><p>La majorité des articles publiés jusqu'ici se référaient à Jekyll,\ncette fois place à <a href=\"https://gohugo.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Hugo</a>. Hugo est un générateur de site\nstatique populaire très performant et beaucoup plus performant pour vos\nvisiteurs si vous lui adjoignez les services d’un Service Worker pour gérer le\nmode déconnecté de votre site web. Notez que les explications fournies ici sont\nvalables et facilement adaptables pour tout autre générateur statique.</p></aside>\n<p>Après le <em>mobile first</em>, place maintenant au <em>offline first</em> et\n<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>\ntous deux très tendances en ce moment. Les Service Workers jouent un rôle majeur\ndans tous les cas de figure. Un Service Worker en gros c'est un script qui va\njouer le rôle d’un proxy entre le navigateur web et le réseau Internet. Vous\ntrouverez dans cet article un exemple simple qui vous permettra d’installer un\nService Worker sur un site statique généré avec <a href=\"https://gohugo.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Hugo</a> afin\nde le rendre ultra-performant.</p>\n<h2 id=\"de-quoi-parle-t-on\">De quoi parle-t-on ?</h2>\n<p>Si vous n'avez pas encore entendu parler des Service Workers et que vous voulez\nen savoir plus sur le sujet, merci de consulter les liens suivants :</p>\n<ul>\n<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>\n<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>\n<li><strong><a href=\"https://ponyfoo.com/articles/serviceworker-revolution\" target=\"_blank\" rel=\"noopener noreferrer\">Service Worker Revolution</a></strong> publié chez Ponyfoo</li>\n<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>\n</ul>\n<p>Maintenant que vous avez lu tout ça — ou du moins que vous avez compris de quoi\nil en retourne — voici ce que nous allons faire :</p>\n<ul>\n<li><strong>Installer un Service Worker</strong> à partir d’un exemple dans Hugo.</li>\n<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>\n<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>\n<li><strong>Ajouter un fichier <code>manifest.json</code></strong> pour définir l’apparence de l’application Web sur mobile.</li>\n</ul>\n<h2 id=\"prerequis\">Prérequis</h2>\n<h3 id=\"creer-une-page-hors-connexion\">Créer une page <code>hors-connexion</code></h3>\n<p>Assurez-vous de créer une page hors-connexion personnalisée pour afficher à vos\nvisiteurs quand ils sont déconnectés du réseau.</p>\n<p>Par exemple vous pouvez créer les fichiers suivants :</p>\n<pre><code class=\"language-sh hljs bash\">├── content\n│   ├── offline.md\n├── layouts\n│   ├── offline/single.html</code></pre>\n<p>Contenu du fichier <strong>content/offline.md</strong> :</p>\n<pre><code class=\"language-md hljs markdown\">+++\ndate = \"2016-10-16T19:28:41+02:00\"\ndraft = false\ntitle = \"Oops, vous êtes déconnecté du réseau.\"\ntype = \"offline\"\n+++\n\nEssayez de vous connecter à Internet pour naviguer sur le site.</code></pre>\n<p>Le fichier <strong>layouts/offline/single.html</strong> :</p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html</span>&gt;</span>\n <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span>\n  <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>\n <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span>\n <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span>\n    <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>\n    </span><span class=\"hljs-template-variable\">{{ .Content }}</span><span class=\"xml\">\n <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></span></code></pre>\n<p>C’est <em>vraiment un exemple minimaliste</em>, vous pouvez bien entendu créer une page\nhors-connexion avec le contenu de votre choix.</p>\n<p>Mais déjà grâce à notre exemple, nous avons généré une page\n<code>offline/index.html</code>. OK, ça c'est fait.</p>\n<h3 id=\"creer-une-page-404-personnalisee\">Créer une page 404 personnalisée</h3>\n<p>Si votre projet ne possède pas encore de page 404 personnalisée, vous pouvez\nvous référer à\n<a href=\"https://gohugo.io/templates/404/\" target=\"_blank\" rel=\"noopener noreferrer\">la documentation d’Hugo pour créer une page 404</a>\nou vous contenter de suivre les quelques instructions de base ci-dessous.</p>\n<p>Pour cela, vous aurez besoin des fichiers suivants :</p>\n<pre><code class=\"language-sh hljs bash\">├──content\n│   ├── 404.md\n├── layouts\n│   ├── 404.html</code></pre>\n<p>Le fichier <strong>content/404.md</strong> :</p>\n<pre><code class=\"language-md hljs markdown\">+++\ndate = \"2016-10-16T19:28:41+02:00\"\ndraft = false\ntitle = \"Zut… Page non trouvée.\"\n+++\n\nVous devriez aller voir ailleurs.</code></pre>\n<p>Le fichier <strong>layouts/404.html</strong> :</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head</span>&gt;</span>\n    <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>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">head</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body</span>&gt;</span>\n    <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>\n    {{ .Content }}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">body</span>&gt;</span>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">html</span>&gt;</span></code></pre>\n<h3 id=\"creer-les-icones-de-l-application-web\">Créer les icônes de l’application Web</h3>\n<p>Les icônes des applications sont juste des favicons qu'on affiche sur un écran\nde démarrage au chargement du site depuis l’écran d’accueil.</p>\n<p>Les tailles suivantes sont recommandées :</p>\n<ul>\n<li>128px × 128px</li>\n<li>144px × 144px</li>\n<li>152px × 152px</li>\n<li>192px × 192px</li>\n<li>256px × 256px</li>\n</ul>\n<p>Pour les générer rapidement, vous pouvez utiliser un service comme\n<a href=\"http://www.favicomatic.com/\" target=\"_blank\" rel=\"noopener noreferrer\">favicomatic.com</a>.</p>\n<p>Ensuite placez les fichiers PNG dans votre dossier <code>/static</code> folder. Par\nexemple :</p>\n<pre><code class=\"language-sh hljs bash\">├── static\n│   ├── favicons\n│   │   ├── icon-128x128.png\n│   │   ├── icon-144x144.png\n│   │   ├── icon-152x152.png\n│   │   ├── icon-192x192.png\n│   │   ├── icon-256x256.png</code></pre>\n<h3 id=\"installation-du-fichier-manifest-json\">Installation du fichier <code>manifest.json</code></h3>\n<p>Le vrai travail commence maintenant avec la création et la configuration du\nfichier <code>manifest.json</code>.</p>\n<p>Nous allons utiliser pour cela un\n<a href=\"https://github.com/wildhaber/offline-first-sw/blob/master/manifest.json\" target=\"_blank\" rel=\"noopener noreferrer\">exemple de fichier manifest</a>\nexistant tiré du dépôt <code>offline-first-sw</code>.</p>\n<p>Placez ce fichier également dans le dossier <code>static/</code>, il doit obligatoirement\nse trouver à la racine comme ceci :</p>\n<pre><code class=\"language-sh hljs bash\">├── static\n│   ├── manifest.json</code></pre>\n<p>Vous pouvez recopier ce fichier à la main ou utiliser la commande suivante si\nvous travaillez dans un environnement GNU Linux ou macOS :</p>\n<pre><code class=\"language-sh hljs bash\"><span class=\"hljs-comment\"># à partir du dossier racine d’Hugo</span>\n<span class=\"hljs-built_in\">cd</span> static\nwget https://raw.githubusercontent.com/wildhaber/offline-first-sw/master/manifest.js</code></pre>\n<p>Vous devriez maintenant avoir un fichier qui ressemble à cela dans votre dossier\n<code>static</code> :</p>\n<pre><code class=\"language-json hljs json\">{\n  <span class=\"hljs-attr\">\"name\"</span>: <span class=\"hljs-string\">\"&lt;nom-de-votre-application&gt;\"</span>,\n  <span class=\"hljs-attr\">\"short_name\"</span>: <span class=\"hljs-string\">\"&lt;nom-abrégé&gt;\"</span>,\n  <span class=\"hljs-attr\">\"icons\"</span>: [\n    {\n      <span class=\"hljs-attr\">\"src\"</span>: <span class=\"hljs-string\">\"/img/icons/logo-128x128.png\"</span>,\n      <span class=\"hljs-attr\">\"sizes\"</span>: <span class=\"hljs-string\">\"128x128\"</span>,\n      <span class=\"hljs-attr\">\"type\"</span>: <span class=\"hljs-string\">\"image/png\"</span>\n    },\n    {\n      <span class=\"hljs-attr\">\"src\"</span>: <span class=\"hljs-string\">\"/img/icons/logo-144x144.png\"</span>,\n      <span class=\"hljs-attr\">\"sizes\"</span>: <span class=\"hljs-string\">\"144x144\"</span>,\n      <span class=\"hljs-attr\">\"type\"</span>: <span class=\"hljs-string\">\"image/png\"</span>\n    },\n    {\n      <span class=\"hljs-attr\">\"src\"</span>: <span class=\"hljs-string\">\"/img/icons/logo-152x152.png\"</span>,\n      <span class=\"hljs-attr\">\"sizes\"</span>: <span class=\"hljs-string\">\"152x152\"</span>,\n      <span class=\"hljs-attr\">\"type\"</span>: <span class=\"hljs-string\">\"image/png\"</span>\n    },\n    {\n      <span class=\"hljs-attr\">\"src\"</span>: <span class=\"hljs-string\">\"/img/icons/logo-192x192.png\"</span>,\n      <span class=\"hljs-attr\">\"sizes\"</span>: <span class=\"hljs-string\">\"192x192\"</span>,\n      <span class=\"hljs-attr\">\"type\"</span>: <span class=\"hljs-string\">\"image/png\"</span>\n    },\n    {\n      <span class=\"hljs-attr\">\"src\"</span>: <span class=\"hljs-string\">\"/img/icons/logo-256x256.png\"</span>,\n      <span class=\"hljs-attr\">\"sizes\"</span>: <span class=\"hljs-string\">\"256x256\"</span>,\n      <span class=\"hljs-attr\">\"type\"</span>: <span class=\"hljs-string\">\"image/png\"</span>\n    }\n  ],\n  <span class=\"hljs-attr\">\"start_url\"</span>: <span class=\"hljs-string\">\"/index.html\"</span>,\n  <span class=\"hljs-attr\">\"display\"</span>: <span class=\"hljs-string\">\"standalone\"</span>,\n  <span class=\"hljs-attr\">\"orientation\"</span>: <span class=\"hljs-string\">\"portrait\"</span>,\n  <span class=\"hljs-attr\">\"background_color\"</span>: <span class=\"hljs-string\">\"#000000\"</span>,\n  <span class=\"hljs-attr\">\"theme_color\"</span>: <span class=\"hljs-string\">\"#000000\"</span>\n}</code></pre>\n<p>Ajustez les valeurs à votre guise.</p>\n<h3 id=\"ajoutez-un-lien-vers-manifest-json-dans-votre-modele\">Ajoutez un lien vers <code>manifest.json</code> dans votre modèle</h3>\n<p>Pour que le navigateur soit en mesure de détecter votre <code>manifest.json</code>, ajoutez\nle bout du code suivant dans le <code>&lt;head&gt;</code> de vos modèles :</p>\n<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>\n<h3 id=\"installation-du-service-worker\">Installation du Service Worker</h3>\n<p>Pour cela nous allons aussi utiliser l’exemple de\n<a href=\"https://github.com/wildhaber/offline-first-sw/blob/master/sw.js\" target=\"_blank\" rel=\"noopener noreferrer\">Service Worker</a>\nfourni dans le dépôt\n<a href=\"https://github.com/wildhaber/offline-first-sw\" target=\"_blank\" rel=\"noopener noreferrer\"><code>offline-first-sw</code></a>.</p>\n<p>Le fichier <code>sw.js</code> doit également se trouver à la racine du dossier <code>static</code>\ncomme ceci :</p>\n<pre><code class=\"language-sh hljs bash\">├── static\n│   ├── sw.js</code></pre>\n<p>Là encore soit vous recopiez le fichier à la main, soit vous utilisez la\ncommande suivante dans un environnement GNU Linux ou macOS :</p>\n<pre><code class=\"language-sh hljs bash\"><span class=\"hljs-comment\"># à partir du dossier racine d’Hugo</span>\n<span class=\"hljs-built_in\">cd</span> static\nwget https://raw.githubusercontent.com/wildhaber/offline-first-sw/master/sw.js</code></pre>\n<p>Vous devez vous retrouver avec le fichier suivant à la racine :</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-keyword\">const</span> CACHE_VERSION = <span class=\"hljs-number\">1</span>;\n\n<span class=\"hljs-keyword\">const</span> BASE_CACHE_FILES = [\n  <span class=\"hljs-string\">'/style.css'</span>,\n  <span class=\"hljs-string\">'/script.js'</span>,\n  <span class=\"hljs-string\">'/search.json'</span>,\n  <span class=\"hljs-string\">'/manifest.json'</span>,\n  <span class=\"hljs-string\">'/favicon.png'</span>,\n];\n\n<span class=\"hljs-keyword\">const</span> OFFLINE_CACHE_FILES = [\n  <span class=\"hljs-string\">'/style.css'</span>,\n  <span class=\"hljs-string\">'/script.js'</span>,\n  <span class=\"hljs-string\">'/offline/index.html'</span>,\n];\n\n<span class=\"hljs-keyword\">const</span> NOT_FOUND_CACHE_FILES = [\n  <span class=\"hljs-string\">'/style.css'</span>,\n  <span class=\"hljs-string\">'/script.js'</span>,\n  <span class=\"hljs-string\">'/404.html'</span>,\n];\n\n<span class=\"hljs-keyword\">const</span> OFFLINE_PAGE = <span class=\"hljs-string\">'/offline/index.html'</span>;\n<span class=\"hljs-keyword\">const</span> NOT_FOUND_PAGE = <span class=\"hljs-string\">'/404.html'</span>;\n\n<span class=\"hljs-keyword\">const</span> CACHE_VERSIONS = {\n  <span class=\"hljs-attr\">assets</span>: <span class=\"hljs-string\">'assets-v'</span> + CACHE_VERSION,\n  <span class=\"hljs-attr\">content</span>: <span class=\"hljs-string\">'content-v'</span> + CACHE_VERSION,\n  <span class=\"hljs-attr\">offline</span>: <span class=\"hljs-string\">'offline-v'</span> + CACHE_VERSION,\n  <span class=\"hljs-attr\">notFound</span>: <span class=\"hljs-string\">'404-v'</span> + CACHE_VERSION,\n};\n\n<span class=\"hljs-comment\">// Durée de mise en cache en SECONDES en fonction des différentes extensions</span>\n<span class=\"hljs-keyword\">const</span> MAX_TTL = {\n  <span class=\"hljs-string\">'/'</span>: <span class=\"hljs-number\">3600</span>,\n  <span class=\"hljs-attr\">html</span>: <span class=\"hljs-number\">3600</span>,\n  <span class=\"hljs-attr\">json</span>: <span class=\"hljs-number\">86400</span>,\n  <span class=\"hljs-attr\">js</span>: <span class=\"hljs-number\">86400</span>,\n  <span class=\"hljs-attr\">css</span>: <span class=\"hljs-number\">86400</span>,\n};\n\n<span class=\"hljs-keyword\">const</span> CACHE_BLACKLIST = [\n  <span class=\"hljs-comment\">//(str) =&gt; {</span>\n  <span class=\"hljs-comment\">//    return !str.startsWith('http://localhost') &amp;&amp; !str.startsWith('https://jamstatic.fr');</span>\n  <span class=\"hljs-comment\">//},</span>\n];\n\n<span class=\"hljs-keyword\">const</span> SUPPORTED_METHODS = [\n  <span class=\"hljs-string\">'GET'</span>,\n];\n\n<span class=\"hljs-comment\">/**\n * isBlackListed\n * @param {string} url\n * @returns {boolean}\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">isBlacklisted</span>(<span class=\"hljs-params\">url</span>) </span>{\n  <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> {\n    <span class=\"hljs-keyword\">if</span> (<span class=\"hljs-keyword\">typeof</span> rule === <span class=\"hljs-string\">'function'</span>) {\n      <span class=\"hljs-keyword\">return</span> !rule(url);\n    } <span class=\"hljs-keyword\">else</span> {\n      <span class=\"hljs-keyword\">return</span> <span class=\"hljs-literal\">false</span>;\n    }\n  }).length : <span class=\"hljs-literal\">false</span>\n}\n\n<span class=\"hljs-comment\">/**\n * getFileExtension\n * <span class=\"hljs-doctag\">@param <span class=\"hljs-type\">{string}</span> <span class=\"hljs-variable\">url</span></span>\n * <span class=\"hljs-doctag\">@returns <span class=\"hljs-type\">{string}</span></span>\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">getFileExtension</span>(<span class=\"hljs-params\">url</span>) </span>{\n  <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>];\n  <span class=\"hljs-keyword\">return</span> (extension.endsWith(<span class=\"hljs-string\">'/'</span>)) ? <span class=\"hljs-string\">'/'</span> : extension;\n}\n\n<span class=\"hljs-comment\">/**\n * getTTL\n * <span class=\"hljs-doctag\">@param <span class=\"hljs-type\">{string}</span> <span class=\"hljs-variable\">url</span></span>\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">getTTL</span>(<span class=\"hljs-params\">url</span>) </span>{\n  <span class=\"hljs-keyword\">if</span> (<span class=\"hljs-keyword\">typeof</span> url === <span class=\"hljs-string\">'string'</span>) {\n    <span class=\"hljs-keyword\">let</span> extension = getFileExtension(url);\n    <span class=\"hljs-keyword\">if</span> (<span class=\"hljs-keyword\">typeof</span> MAX_TTL[extension] === <span class=\"hljs-string\">'number'</span>) {\n      <span class=\"hljs-keyword\">return</span> MAX_TTL[extension];\n    } <span class=\"hljs-keyword\">else</span> {\n      <span class=\"hljs-keyword\">return</span> <span class=\"hljs-literal\">null</span>;\n    }\n  } <span class=\"hljs-keyword\">else</span> {\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-literal\">null</span>;\n  }\n}\n\n<span class=\"hljs-comment\">/**\n * installServiceWorker\n * <span class=\"hljs-doctag\">@returns <span class=\"hljs-type\">{Promise}</span></span>\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">installServiceWorker</span>(<span class=\"hljs-params\"></span>) </span>{\n  <span class=\"hljs-keyword\">return</span> <span class=\"hljs-built_in\">Promise</span>.all(\n    [\n      caches.open(CACHE_VERSIONS.assets)\n      .then(\n        <span class=\"hljs-function\">(<span class=\"hljs-params\">cache</span>) =&gt;</span> {\n          <span class=\"hljs-keyword\">return</span> cache.addAll(BASE_CACHE_FILES);\n        }\n      ),\n      caches.open(CACHE_VERSIONS.offline)\n      .then(\n        <span class=\"hljs-function\">(<span class=\"hljs-params\">cache</span>) =&gt;</span> {\n          <span class=\"hljs-keyword\">return</span> cache.addAll(OFFLINE_CACHE_FILES);\n        }\n      ),\n      caches.open(CACHE_VERSIONS.notFound)\n      .then(\n        <span class=\"hljs-function\">(<span class=\"hljs-params\">cache</span>) =&gt;</span> {\n          <span class=\"hljs-keyword\">return</span> cache.addAll(NOT_FOUND_CACHE_FILES);\n        }\n      )\n    ]\n  );\n}\n\n<span class=\"hljs-comment\">/**\n * cleanupLegacyCache\n * <span class=\"hljs-doctag\">@returns <span class=\"hljs-type\">{Promise}</span></span>\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> <span class=\"hljs-title\">cleanupLegacyCache</span>(<span class=\"hljs-params\"></span>) </span>{\n\n  <span class=\"hljs-keyword\">let</span> currentCaches = <span class=\"hljs-built_in\">Object</span>.keys(CACHE_VERSIONS)\n    .map(\n      <span class=\"hljs-function\">(<span class=\"hljs-params\">key</span>) =&gt;</span> {\n        <span class=\"hljs-keyword\">return</span> CACHE_VERSIONS[key];\n      }\n    );\n\n  <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Promise</span>(\n    <span class=\"hljs-function\">(<span class=\"hljs-params\">resolve, reject</span>) =&gt;</span> {\n\n      caches.keys()\n        .then(\n          <span class=\"hljs-function\">(<span class=\"hljs-params\">keys</span>) =&gt;</span> {\n            <span class=\"hljs-keyword\">return</span> legacyKeys = keys.filter(\n              <span class=\"hljs-function\">(<span class=\"hljs-params\">key</span>) =&gt;</span> {\n                <span class=\"hljs-keyword\">return</span> !~currentCaches.indexOf(key);\n              }\n            );\n          }\n        )\n        .then(\n          <span class=\"hljs-function\">(<span class=\"hljs-params\">legacy</span>) =&gt;</span> {\n            <span class=\"hljs-keyword\">if</span> (legacy.length) {\n              <span class=\"hljs-built_in\">Promise</span>.all(\n                  legacy.map(\n                    <span class=\"hljs-function\">(<span class=\"hljs-params\">legacyKey</span>) =&gt;</span> {\n                      <span class=\"hljs-keyword\">return</span> caches.delete(legacyKey)\n                    }\n                  )\n                )\n                .then(\n                  <span class=\"hljs-function\"><span class=\"hljs-params\">()</span> =&gt;</span> {\n                    resolve()\n                  }\n                )\n                .catch(\n                  <span class=\"hljs-function\">(<span class=\"hljs-params\">err</span>) =&gt;</span> {\n                    reject(err);\n                  }\n                );\n            } <span class=\"hljs-keyword\">else</span> {\n              resolve();\n            }\n          }\n        )\n        .catch(\n          <span class=\"hljs-function\"><span class=\"hljs-params\">()</span> =&gt;</span> {\n            reject();\n          }\n        );\n\n    }\n  );\n}\n\n\nself.addEventListener(\n  <span class=\"hljs-string\">'install'</span>, event =&gt; {\n    event.waitUntil(installServiceWorker());\n  }\n);\n\n<span class=\"hljs-comment\">// La méthode activate est chargée de supprimer les vieux caches</span>\nself.addEventListener(\n  <span class=\"hljs-string\">'activate'</span>, event =&gt; {\n    event.waitUntil(\n      <span class=\"hljs-built_in\">Promise</span>.all(\n        [\n          cleanupLegacyCache(),\n        ]\n      )\n      .catch(\n        <span class=\"hljs-function\">(<span class=\"hljs-params\">err</span>) =&gt;</span> {\n          event.skipWaiting();\n        }\n      )\n    );\n  }\n);\n\nself.addEventListener(\n  <span class=\"hljs-string\">'fetch'</span>, event =&gt; {\n\n    event.respondWith(\n      caches.open(CACHE_VERSIONS.content)\n      .then(\n        <span class=\"hljs-function\">(<span class=\"hljs-params\">cache</span>) =&gt;</span> {\n\n          <span class=\"hljs-keyword\">return</span> cache.match(event.request)\n            .then(\n              <span class=\"hljs-function\">(<span class=\"hljs-params\">response</span>) =&gt;</span> {\n\n                <span class=\"hljs-keyword\">if</span> (response) {\n\n                  <span class=\"hljs-keyword\">let</span> headers = response.headers.entries();\n                  <span class=\"hljs-keyword\">let</span> date = <span class=\"hljs-literal\">null</span>;\n\n                  <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">let</span> pair <span class=\"hljs-keyword\">of</span> headers) {\n                    <span class=\"hljs-keyword\">if</span> (pair[<span class=\"hljs-number\">0</span>] === <span class=\"hljs-string\">'date'</span>) {\n                      date = <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Date</span>(pair[<span class=\"hljs-number\">1</span>]);\n                    }\n                  }\n\n                  <span class=\"hljs-keyword\">if</span> (date) {\n                    <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>);\n                    <span class=\"hljs-keyword\">let</span> ttl = getTTL(event.request.url);\n\n                    <span class=\"hljs-keyword\">if</span> (ttl &amp; amp; &amp; amp; age &gt; ttl) {\n\n                      <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">new</span> <span class=\"hljs-built_in\">Promise</span>(\n                          <span class=\"hljs-function\">(<span class=\"hljs-params\">resolve</span>) =&gt;</span> {\n\n                            <span class=\"hljs-keyword\">return</span> fetch(event.request)\n                              .then(\n                                <span class=\"hljs-function\">(<span class=\"hljs-params\">updatedResponse</span>) =&gt;</span> {\n                                  <span class=\"hljs-keyword\">if</span> (updatedResponse) {\n                                    cache.put(event.request, updatedResponse.clone());\n                                    resolve(updatedResponse);\n                                  } <span class=\"hljs-keyword\">else</span> {\n                                    resolve(response)\n                                  }\n                                }\n                              )\n                              .catch(\n                                <span class=\"hljs-function\"><span class=\"hljs-params\">()</span> =&gt;</span> {\n                                  resolve(response);\n                                }\n                              );\n\n                          }\n                        )\n                        .catch(\n                          <span class=\"hljs-function\">(<span class=\"hljs-params\">err</span>) =&gt;</span> {\n                            <span class=\"hljs-keyword\">return</span> response;\n                          }\n                        );\n                    } <span class=\"hljs-keyword\">else</span> {\n                      <span class=\"hljs-keyword\">return</span> response;\n                    }\n\n                  } <span class=\"hljs-keyword\">else</span> {\n                    <span class=\"hljs-keyword\">return</span> response;\n                  }\n\n                } <span class=\"hljs-keyword\">else</span> {\n                  <span class=\"hljs-keyword\">return</span> <span class=\"hljs-literal\">null</span>;\n                }\n              }\n            )\n            .then(\n              <span class=\"hljs-function\">(<span class=\"hljs-params\">response</span>) =&gt;</span> {\n                <span class=\"hljs-keyword\">if</span> (response) {\n                  <span class=\"hljs-keyword\">return</span> response;\n                } <span class=\"hljs-keyword\">else</span> {\n                  <span class=\"hljs-keyword\">return</span> fetch(event.request)\n                    .then(\n                      <span class=\"hljs-function\">(<span class=\"hljs-params\">response</span>) =&gt;</span> {\n\n                        <span class=\"hljs-keyword\">if</span> (response.status &lt; <span class=\"hljs-number\">400</span>) {\n                          <span class=\"hljs-keyword\">if</span> (~SUPPORTED_METHODS.indexOf(event.request.method) &amp; amp; &amp; amp; !isBlacklisted(event.request.url)) {\n                            cache.put(event.request, response.clone());\n                          }\n                          <span class=\"hljs-keyword\">return</span> response;\n                        } <span class=\"hljs-keyword\">else</span> {\n                          <span class=\"hljs-keyword\">return</span> caches.open(CACHE_VERSIONS.notFound).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">cache</span>) =&gt;</span> {\n                            <span class=\"hljs-keyword\">return</span> cache.match(NOT_FOUND_PAGE);\n                          })\n                        }\n                      }\n                    )\n                    .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">response</span>) =&gt;</span> {\n                      <span class=\"hljs-keyword\">if</span> (response) {\n                        <span class=\"hljs-keyword\">return</span> response;\n                      }\n                    })\n                    .catch(\n                      <span class=\"hljs-function\"><span class=\"hljs-params\">()</span> =&gt;</span> {\n\n                        <span class=\"hljs-keyword\">return</span> caches.open(CACHE_VERSIONS.offline)\n                          .then(\n                            <span class=\"hljs-function\">(<span class=\"hljs-params\">offlineCache</span>) =&gt;</span> {\n                              <span class=\"hljs-keyword\">return</span> offlineCache.match(OFFLINE_PAGE)\n                            }\n                          )\n\n                      }\n                    );\n                }\n              }\n            )\n            .catch(\n              <span class=\"hljs-function\">(<span class=\"hljs-params\">error</span>) =&gt;</span> {\n                <span class=\"hljs-built_in\">console</span>.error(<span class=\"hljs-string\">'Error in fetch handler:'</span>, error);\n                <span class=\"hljs-keyword\">throw</span> error;\n              }\n            );\n        }\n      )\n    );\n\n  }\n);</code></pre>\n<p>Maintenant vous pouvez définir le comportement souhaité pour votre Service\nWorker :</p>\n<h4 id=\"fichiers-a-mettre-en-cache-par-defaut\">Fichiers à mettre en cache par défaut</h4>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-keyword\">const</span> BASE_CACHE_FILES = [\n  <span class=\"hljs-string\">\"/style.css\"</span>,\n  <span class=\"hljs-string\">\"/script.js\"</span>,\n  <span class=\"hljs-string\">\"/search.json\"</span>,\n  <span class=\"hljs-string\">\"/manifest.json\"</span>,\n  <span class=\"hljs-string\">\"/favicon.png\"</span>,\n];</code></pre>\n<p>Listez dans ce tableau tous les fichiers qui devraient être mis en cache par\ndéfaut</p>\n<h4 id=\"fichiers-en-mode-hors-connexion\">Fichiers en mode hors-connexion</h4>\n<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>\n<p>Listez dans ce tableau les fichiers nécessaires pour l’affichage de votre page\n<code>offline</code>.</p>\n<h4 id=\"fichiers-en-cas-d-erreur-4xx\">Fichiers en cas d’erreur 4xx</h4>\n<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>\n<p>Listez dans ce tableau les fichiers nécessaires pour l’affichage de votre page\nd’erreur 404.</p>\n<h4 id=\"page-hors-connexion\">Page hors-connexion</h4>\n<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>\n<p>C’est la page qui sera affichée quand le visiteur sera déconnecté du réseau ou\nque la page n'est pas déjà en cache.</p>\n<h4 id=\"page-d-erreur\">Page d’erreur</h4>\n<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>\n<p>Le chemin de la page qui sera affichée en cas d’erreur de type 4xx.</p>\n<h4 id=\"duree-de-mise-en-cache\">Durée de mise en cache</h4>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-keyword\">const</span> MAX_TTL = {\n  <span class=\"hljs-string\">\"/\"</span>: <span class=\"hljs-number\">3600</span>,\n  <span class=\"hljs-attr\">html</span>: <span class=\"hljs-number\">3600</span>,\n  <span class=\"hljs-attr\">json</span>: <span class=\"hljs-number\">86400</span>,\n  <span class=\"hljs-attr\">js</span>: <span class=\"hljs-number\">86400</span>,\n  <span class=\"hljs-attr\">css</span>: <span class=\"hljs-number\">86400</span>,\n};</code></pre>\n<p>Ce tableau clé-valeur indique pour chaque type d’extension de fichier la durée\nmaximum de mise en cache appelée <em>Time To Live</em> (définit <strong>en secondes</strong> et pas\nen millisecondes). C’est le temps qui s'écoulera avant qu'un fichier ne soit mis\nà jour à partir du réseau.</p>\n<p>Les extensions non présentes resteront en cache jusqu'à la prochaine la mise à\njour du cache par le Service Worker.</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-comment\">// 60 = 1 minute</span>\n<span class=\"hljs-comment\">// 3600 = 1 heure</span>\n<span class=\"hljs-comment\">// 86400 = 1 jour</span>\n<span class=\"hljs-comment\">// 604800 = 1 semaine</span>\n<span class=\"hljs-comment\">// 2592000 = 30 jours (~ 1 mois)</span>\n<span class=\"hljs-comment\">// 31536000 = 1 an</span></code></pre>\n<h4 id=\"fichiers-a-exclure-de-la-mise-en-cache\">Fichiers à exclure de la mise en cache</h4>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-keyword\">const</span> CACHE_BLACKLIST = [\n  <span class=\"hljs-function\">(<span class=\"hljs-params\">str</span>) =&gt;</span> {\n    <span class=\"hljs-comment\">// str = URL de la ressource</span>\n    <span class=\"hljs-comment\">// Appliquez cette règle lorsque vous ne voulez pas mettre des fichiers externes en cache</span>\n    <span class=\"hljs-keyword\">return</span> !str.startsWith(<span class=\"hljs-string\">\"https://votresiteweb.tld\"</span>);\n  },\n];</code></pre>\n<p>Ajustez ces paramètres au contexte de votre site ou de votre application.</p>\n<h3 id=\"enregistrement-du-service-worker\">Enregistrement du Service Worker</h3>\n<p>Ajoutez le script suivant avant la fermeture de la balise <code>&lt;body&gt;</code> ou placez-le\ndans votre fichier JavaScript généré :</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script</span>&gt;</span><span class=\"javascript\">\n  <span class=\"hljs-keyword\">if</span> (<span class=\"hljs-string\">\"serviceWorker\"</span> <span class=\"hljs-keyword\">in</span> navigator) {\n    navigator.serviceWorker\n      .register(<span class=\"hljs-string\">\"/sw.js\"</span>, { <span class=\"hljs-attr\">scope</span>: <span class=\"hljs-string\">\"/\"</span> })\n      .then(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">registration</span>) </span>{\n        <span class=\"hljs-built_in\">console</span>.log(<span class=\"hljs-string\">\"Service Worker enregistré\"</span>);\n      });\n\n    navigator.serviceWorker.ready.then(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">registration</span>) </span>{\n      <span class=\"hljs-built_in\">console</span>.log(<span class=\"hljs-string\">\"Service Worker prêt\"</span>);\n    });\n  }\n</span><span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">script</span>&gt;</span></code></pre>\n<p>Ce code JS va enregistrer, installer et activer votre Service Worker.</p>\n<p>Vous en avez à présent terminé avec toutes les étapes nécessaires. Vous disposez\nmaintenant d’un site Hugo ultra-rapide. :)</p>\n<h3 id=\"deboguer-votre-service-worker\">Déboguer votre Service Worker</h3>\n<p>Pour déboguer un Service Worker avec Google Chrome, il vous suffit d’ouvrir la\nconsole et d’aller dans l’onglet <code>Application</code>. C’est là que vous trouverez\nvotre Service Worker et vos caches.</p>\n<p>Vous en apprendrez davantage sur le\n<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>\nsur le site pour les développeurs de Google.</p>\n<p>Si votre navigateur préféré est Firefox vous en saurez plus sur\n<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>\nsur hacks.mozilla.org.</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/02/23/creer-un-environnement-de-preproduction-pour-jekyll/",
      "url": "https://jamstatic.fr/2017/02/23/creer-un-environnement-de-preproduction-pour-jekyll/",
      "title": "Créer un environnement de préproduction pour Jekyll",
      "summary": "Netlify vous permet de créer simplement un environnement de préproduction pour votre site statique.",
      "date_published": "2017-02-23T00:00:00+00:00","content_text": "Dans son article, Eduardo Boucas révèle comment il\na mis simplement en place un site de préproduction pour Jekyll grâce à Netlify.\nCe blog est également déployé et hébergé grâce à Netlify. La nouvelle version de\nleur interface d’administration propose nativement des fonctionnalités comme le\nfait d’associer un sous-domaine à une branche ou le fait de pouvoir bloquer le\nsite de production à un certain commit. Vous n'avez donc pas besoin de créer\ndeux sites dans Netlify pour bénéficier d’une prévisualisation sur une URL\ndédiée. L'article d’Eduardo aborde néanmoins l’utilisation des variables\nd’environnements et utilise Jekyll comme exemple, la technique reste bien\nentendu valable pour d’autres générateurs comme Hugo, Hexo et les autres.\nUn environnement de préproduction ou de staging est une infrastructure de test\nqui s'approche autant que possible de la configuration d’un site de production.\nDans le cas d’un site statique, il peut servir à partager un nouvel article ou\nune nouvelle fonctionnalité avec un nombre de personnes restreintes avant de le\nrendre disponible publiquement. Dans cet article, je vais vous montrer comment\nj'ai fait pour en créer un et comment je m'en sers.\nLe workflow Git\nMon site est hébergé sur GitHub et servi grâce à\nGitHub Pages, ce qui signifie que tout ce que je\npousse sur la branche master déclenche une régénération du site et est publié\npresque immédiatement. Si je visite l’URL de mon site quelques secondes plus\ntard, je peux voir les contenus mis à jour.\nPour notre environnement de préproduction, ce que nous voulons c'est faire une\ncopie basique de cette infrastructure de manière à faire passer notre contenu\npar un site distinct, avec une URL distincte. La démarche ressemblerait à\nquelque chose comme :\n\nPousser les changements sur GitHub\nLe site de préproduction est régénéré, l’URL de préproduction peut être utilisée pour prévisualiser le nouvel état\nCréer une pull request de la préproduction vers la production pour répercuter les changements\nLe site de production est régénéré, l’URL de production reflète le nouvel état du site\n\nPour que notre système fonctionne uniquement avec GitHub Pages nous allons avoir\nbesoin de deux dépôts, puisqu'on ne peut pas servir deux sites à partir d’un\nseul dépôt. Mais à moins qu'un dépôt ne soit le fork d’un autre, ce qui signifie\navoir deux comptes GitHub ou utiliser un compte organisation, vous ne pouvez\npas créer de pull request entre eux.\nJ'utilise donc Netlify à la place pour servir mon site de\npréproduction à partir de la branche dev de mon dépôt existant - GitHub Pages\nsert eduardoboucas.com à partir de la branche master et Netlify sert\ndev.eduardoboucas.com à partir de la branche dev.\nPour créer cette branche, vous pouvez utiliser la commande suivante :\n# Partir de la branche actuelle et en créer une nouvelle appelée dev\ngit checkout -b dev\n\n# Pousser la nouvelle branche sur le dépôt distant\ngit push origin dev\nUtilisation de Netlify\nPour commencer à utiliser Netlify, rendez-vous sur\nleur site et connectez-vous avec votre compte GitHub\n(c'est gratuit pour les projets open-source).\nCliquez sur Ajoutez un nouveau projet, sélectionnez GitHub et sélectionnez le\ndépôt qui dans lequel se trouve votre site.\n\n\n\n\n\n\nNetlify : Configuration du dépôt\n\nDans l’onglet Paramètres de base, sélectionnez votre branche de préproduction\n(par exemple dev). Pour un site avec Jekyll, le dossier de publication par\ndéfaut est _site et la commande de build jekyll build. Dans l’onglet\nParamètres avancés, ajouter une variable d’environnement nommée JEKYLL_ENV\navec la valeur stage — cela va servir à dire à Jekyll dans quel environnement\ntourne le site.\nEnsuite, cliquez sur Générer votre site et attendez quelques secondes. Lorsque\nla génération du site est terminée, cliquez sur Voir votre site pour voir le\nrésultat.\nUn nom aléatoire vous sera attribué, comme cartoonist-foreground-47121, que\nvous pourrez ensuite modifier dans les paramètres. Vous pouvez également définir\nun nom de domaine personnalisé pour ce site, pour cela vous devrez configurer\nvotre DNS. Si vous avez choisi dev-example-com comme nom pour votre site, il\nvous faudra un CNAME qui pointe vers dev-example-com.netlify.com.\n\n\n\n\n\n\nNetlify : le panneau de configuration\n\nConfiguration de Jekyll\nOn peut accéder à la variable d’environnement que nous avons créé plus haut dans\nJekyll, en utilisant la variable jekyll.environment dans n'importe quel modèle\nLiquid. Grâce à ça, nous pouvons effectuer quelques ajustements au site en\nfonction de l’environnement dans lequel il tourne.\nPar exemple, nous ne voulons pas que le site de préproduction soit indexé par\nles moteurs de recherche.\n{% if jekyll.environment == 'stage' %}\n  &lt;meta name=\"robots\" content=\"noindex\"&gt;\n{% endif %}\nVous pouvez même ajouter une bannière en haut de chaque page, pour avertir les\nvisiteurs qu'ils consultent la version de développement de votre site.\n{% if jekyll.environment == 'stage' %}\n  &lt;p class=\"banner\"&gt;\n    &lt;a href=\"https:\/\/eduardoboucas.com\"&gt;\n      Ceci est la version de développement du site. Cliquez ici pour voir la version de production.\n    &lt;\/a&gt;\n  &lt;\/p&gt;\n{% endif %}\nEt voilà mon site de préproduction est\nconfiguré. Lorsque je veux demander à quelqu'un de relire un article avant qu'il\nne soit publié, je le pousse sur la branche dev pour le publier sur le site de\npréproduction, pour le publier en production, j'ai juste à le fusionner dans la\nbranche master.\nVous pourriez même vous passer complètement de GitHub Pages (NdT: et de ses\nlimitations) et vous reposer entièrement sur Netlify pour servir vos\nenvironnements de production et de préproduction (ce que je risque de faire\nbientôt pour des raisons que je dévoilerai dans un prochain article)\nEt voilà, vous avez maintenant un environnement de préproduction simple avec\nintégration continue pour un site statique et tout ça gratuitement. Pas mal,\nnon ?",
      "content_html": "<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\na 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>.\nCe blog est également déployé et hébergé grâce à Netlify. La nouvelle version de\nleur interface d’administration propose nativement des fonctionnalités comme le\nfait d’associer un sous-domaine à une branche ou le fait de pouvoir bloquer le\nsite de production à un certain commit. Vous n'avez donc pas besoin de créer\ndeux sites dans Netlify pour bénéficier d’une prévisualisation sur une URL\ndédiée. L'article d’Eduardo aborde néanmoins l’utilisation des variables\nd’environnements et utilise Jekyll comme exemple, la technique reste bien\nentendu valable pour d’autres générateurs comme Hugo, Hexo et les autres.</p></aside>\n<p>Un environnement de préproduction ou de <em>staging</em> est une infrastructure de test\nqui s'approche autant que possible de la configuration d’un site de production.\nDans le cas d’un site statique, il peut servir à partager un nouvel article ou\nune nouvelle fonctionnalité avec un nombre de personnes restreintes avant de le\nrendre disponible publiquement. Dans cet article, je vais vous montrer comment\nj'ai fait pour en créer un et comment je m'en sers.</p>\n<h2 id=\"le-workflow-git\">Le workflow Git</h2>\n<p>Mon site est hébergé sur GitHub et servi grâce à\n<a href=\"https://pages.github.com/\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Pages</a>, ce qui signifie que tout ce que je\npousse sur la branche <code>master</code> déclenche une régénération du site et est publié\npresque immédiatement. Si je visite l’URL de mon site quelques secondes plus\ntard, je peux voir les contenus mis à jour.</p>\n<p>Pour notre environnement de préproduction, ce que nous voulons c'est faire une\ncopie basique de cette infrastructure de manière à faire passer notre contenu\npar un site distinct, avec une URL distincte. La démarche ressemblerait à\nquelque chose comme :</p>\n<ol>\n<li>Pousser les changements sur GitHub</li>\n<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>\n<li>Créer une pull request de la préproduction vers la production pour répercuter les changements</li>\n<li>Le site de production est régénéré, l’URL de production reflète le nouvel état du site</li>\n</ol>\n<p>Pour que notre système fonctionne uniquement avec GitHub Pages nous allons avoir\nbesoin de deux dépôts, puisqu'on ne peut pas servir deux sites à partir d’un\nseul dépôt. Mais à moins qu'un dépôt ne soit le fork d’un autre, ce qui signifie\navoir deux comptes GitHub ou utiliser un compte <em>organisation</em>, vous ne pouvez\npas créer de pull request entre eux.</p>\n<p>J'utilise donc <a href=\"https://netlify.com\" target=\"_blank\" rel=\"noopener noreferrer\">Netlify</a> à la place pour servir mon site de\npréproduction à partir de la branche <code>dev</code> de mon dépôt existant - GitHub Pages\nsert <code>eduardoboucas.com</code> à partir de la branche <code>master</code> et Netlify sert\n<code>dev.eduardoboucas.com</code> à partir de la branche <code>dev</code>.</p>\n<p>Pour créer cette branche, vous pouvez utiliser la commande suivante :</p>\n<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>\ngit checkout -b dev\n\n<span class=\"hljs-comment\"># Pousser la nouvelle branche sur le dépôt distant</span>\ngit push origin dev</code></pre>\n<h2 id=\"utilisation-de-netlify\">Utilisation de Netlify</h2>\n<p>Pour commencer à utiliser Netlify, rendez-vous sur\n<a href=\"https://netlify.com\" target=\"_blank\" rel=\"noopener noreferrer\">leur site</a> et connectez-vous avec votre compte GitHub\n(c'est <a href=\"https://netlify.com/pricing/\" target=\"_blank\" rel=\"noopener noreferrer\">gratuit pour les projets open-source</a>).\nCliquez sur <code>Ajoutez un nouveau projet</code>, sélectionnez GitHub et sélectionnez le\ndépôt qui dans lequel se trouve votre site.</p>\n<figure>\n<picture title=\"Netlify : Configuration du dépôt\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Netlify : Configuration du dépôt</figcaption>\n</figure>\n<p>Dans l’onglet <code>Paramètres de base</code>, sélectionnez votre branche de préproduction\n(par exemple <code>dev</code>). Pour un site avec Jekyll, le dossier de publication par\ndéfaut est <code>_site</code> et la commande de build <code>jekyll build</code>. Dans l’onglet\n<code>Paramètres avancés</code>, ajouter une variable d’environnement nommée <code>JEKYLL_ENV</code>\navec la valeur <code>stage</code> — cela va servir à dire à Jekyll dans quel environnement\ntourne le site.</p>\n<p>Ensuite, cliquez sur <code>Générer votre site</code> et attendez quelques secondes. Lorsque\nla génération du site est terminée, cliquez sur <code>Voir votre site</code> pour voir le\nrésultat.</p>\n<p>Un nom aléatoire vous sera attribué, comme <code>cartoonist-foreground-47121</code>, que\nvous pourrez ensuite modifier dans les paramètres. Vous pouvez également définir\nun nom de domaine personnalisé pour ce site, pour cela vous devrez configurer\nvotre DNS. Si vous avez choisi <code>dev-example-com</code> comme nom pour votre site, il\nvous faudra un CNAME qui pointe vers <code>dev-example-com.netlify.com</code>.</p>\n<figure>\n<picture title=\"Netlify : le panneau de configuration\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Netlify : le panneau de configuration</figcaption>\n</figure>\n<h2 id=\"configuration-de-jekyll\">Configuration de Jekyll</h2>\n<p>On peut accéder à la variable d’environnement que nous avons créé plus haut dans\nJekyll, en utilisant la variable <code>jekyll.environment</code> dans n'importe quel modèle\nLiquid. Grâce à ça, nous pouvons effectuer quelques ajustements au site en\nfonction de l’environnement dans lequel il tourne.</p>\n<p>Par exemple, nous ne voulons pas que le site de préproduction soit indexé par\nles moteurs de recherche.</p>\n<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\">\n  <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>\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> %}</span></code></pre>\n<p>Vous pouvez même ajouter une bannière en haut de chaque page, pour avertir les\nvisiteurs qu'ils consultent la version de développement de votre site.</p>\n<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\">\n  <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>\n    <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>\n      Ceci est la version de développement du site. Cliquez ici pour voir la version de production.\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">a</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">p</span>&gt;</span>\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endif</span></span> %}</span></code></pre>\n<p>Et voilà mon <a href=\"https://dev.eduardoboucas.com\" target=\"_blank\" rel=\"noopener noreferrer\">site de préproduction</a> est\nconfiguré. Lorsque je veux demander à quelqu'un de relire un article avant qu'il\nne soit publié, je le pousse sur la branche <code>dev</code> pour le publier sur le site de\npréproduction, pour le publier en production, j'ai juste à le fusionner dans la\nbranche <code>master</code>.</p>\n<p>Vous pourriez même vous passer complètement de GitHub Pages (NdT: et de ses\nlimitations) et vous reposer entièrement sur Netlify pour servir vos\nenvironnements de production et de préproduction (ce que je risque de faire\nbientôt pour des raisons que je dévoilerai dans un prochain article)</p>\n<p>Et voilà, vous avez maintenant un environnement de préproduction simple avec\nintégration continue pour un site statique et tout ça gratuitement. Pas mal,\nnon ?</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/02/10/creer-des-mises-en-page-dynamiques-avec-jekyll/",
      "url": "https://jamstatic.fr/2017/02/10/creer-des-mises-en-page-dynamiques-avec-jekyll/",
      "title": "Créer des mises en page dynamiques avec Jekyll",
      "summary": "Les secrets de fabrication du portfolio client d’une agence à l’aide des possibilités offertes par Jekyll",
      "date_published": "2017-02-10T00:00:00+00:00","content_text": "Dans son article publié sur Medium, 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.\n\nVoici comment nous avons tiré profit du YAML front matter pour pouvoir\neffectuer de nombreux changements au sein d’un même modèle pour différents\narticles sur le nouveau site de notre agence.\n\n\n\n\n\n\n\nExtrait de la campagne Canon #unleashprint\n\nLes sites web créés avec Jekyll 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 le nouveau site de TMW, en utilisant du YAML front matter et un peu de magie1…\nChez 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…\nDu YAML front matter, une boucle for et pas mal de modules\nAprè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.\nLe YAML front matter\nNous avons ajouté un tableau YAML partials 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é name qui correspond au name du partial\/module qui sera utilisé.\n\nLa boucle for\nLa boucle en question a été ajoutée dans le modèle work du dossier _layouts. Ç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.\nLa boucle parcourt le tableau partials et utilise la propriété name pour inclure un module différent, comme ceci: include {{item.name}}.html. Ceux-ci ont ensuite été transmis au module inclus en utilisant les paramètres suivants de la balise include. Même si la propriété n'était pas nécessaire dans ce module, elle devait néanmoins être transmise.\nTrès vite, j'ai compris que les propriétés du tableau partials devaient partager les mêmes propriétés pour que ma boucle for ne parte pas en sucette.\n\nLes modules\nCré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 {% if include.spaced %}), 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 section-image à une colonne égale à la largeur de l’élément .l-container ou à une version un peu plus large via l’utilisation de la classe .l-container--wide. Cette technique a été utilisé avec le plus bel effet sur l’étude de case de Lynx Bigger Issues.\n\nLes palettes de couleur\nUne 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.\nLes couleurs primaires, secondaires ainsi que celles du texte sont définies dans du YAML et ajoutées ensuite dans un petit bloc &lt;style&gt; embarqué dans la page et qui modifie certains aspects de la présentation de la page.\n\nGrâce à tout cela, nous avons pu ajouter pas mal de personnalisation à notre nouveau site web, ci-dessous une sélection d’études de cas :\n\n\n\n\n\n\n\n\n\n\n\nhttps:\/\/www.tmwunlimited.com\/work\/\n\n\n\n\n\n\n\n\n\n\n\n👋\nComme 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.\n\n\n\n\nRéalisé sans trucages&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<blockquote>\n<p>Voici comment nous avons tiré profit du YAML front matter pour pouvoir\neffectuer de nombreux changements au sein d’un même modèle pour différents\narticles sur le nouveau site de notre agence.</p>\n</blockquote>\n<figure>\n<picture title=\"Extrait de la campagne Canon #unleashprint\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Extrait de la campagne Canon <a href=\"https://www.tmwunlimited.com/work/canon-unleashprint\" target=\"_blank\" rel=\"noopener noreferrer\">#unleashprint</a></figcaption>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<h3 id=\"le-yaml-front-matter\">Le YAML front matter</h3>\n<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>\n<p><script src=\"https://gist.github.com/mrmartineau/ee7cd73fcfdef19b45afd01c4d6b3b9f.js\"></p>\n<h3 id=\"la-boucle-for\">La boucle <code>for</code></h3>\n<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>\n<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>\n<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>\n<p><script src=\"https://gist.github.com/mrmartineau/e0ad7ae56552c9571e285e30e3469476.js\"></p>\n<h3 id=\"les-modules\">Les modules</h3>\n<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>\n<p><script src=\"https://gist.github.com/mrmartineau/8919159d58818c8530d516d118c3b838.js\"></p>\n<h3 id=\"les-palettes-de-couleur\">Les palettes de couleur</h3>\n<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>\n<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>\n<p><script src=\"https://gist.github.com/mrmartineau/01bde36c11a6b799112148a8dc83cc16.js\"></p>\n<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>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<figure>\n<picture title=\"https://www.tmwunlimited.com/work/\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption><a href=\"https://www.tmwunlimited.com/work/\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.tmwunlimited.com/work/</a></figcaption>\n</figure>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<h3 id=\"YoYaSL\">👋</h3>\n<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>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>Réalisé sans trucages&#160;<a href=\"#fnref1:1\" rev=\"footnote\" class=\"footnote-backref\">&#8617;</a></p>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/02/09/y-a-quoi-dans-un-generateur-de-site-statique/",
      "url": "https://jamstatic.fr/2017/02/09/y-a-quoi-dans-un-generateur-de-site-statique/",
      "title": "Qu’y a-t-il dans un générateur de site statique ?",
      "summary": "Jetons un coup d’œil au fonctionnement interne de Harp, un générateur de site statique écrit en JavaScript",
      "date_published": "2017-02-09T15:21:52+00:00","content_text": "Après être aller regarder sous le capot de Jekyll et toujours dans l’idée de continuer à nous\nfamiliariser avec différents générateurs de site statique, voici la traduction\nd’un article de Brian Rinaldi paru chez\nCSS-tricks, qui\nnous entraîne cette fois-ci dans les entrailles de Harp,\nun générateur de fichiers statiques développé en JavaScript, qui résume bien le\npérimètre fonctionnel de ces outils.\nJe parle beaucoup des générateurs de site statique, mais je parle toujours de\ncomment utiliser des générateurs de site statique. Ils sont souvent perçus\ncomme une boîte noire. Je crée un modèle, j'écris un peu de Markdown et hop\nj'obtiens une page entièrement formatée en HTML. C’est magique !\nMais qu'est-ce vraiment un générateur de site statique ? Qu’y a-t-il dans cette\nboîte noire ? De quelle magie Vaudou s'agit-il ?\nDans cet article, nous allons examiner les différentes parties qui constituent\nun générateur de site statique. Premièrement, nous parlerons des généralités et\nensuite nous regarderons de plus près un vrai bout de code source en plongeant\nau plus profond d’HarpJS. Alors, enfilez votre casquette\nd’aventurier et partons en exploration.\nPourquoi Harp ? Pour deux raisons. La première est qu'HarpJS est, de par sa\nconception, un générateur de site statique très simple. Il ne possède pas\nbeaucoup de fonctionnalités qui feraient que nous nous perdrions en route en\nvoulant explorer un générateur de site statique plus complet (comme\nJekyll par exemple). La deuxième raison à cela, bien\nplus pragmatique, est que je connais bien JavaScript alors que je ne connais pas\ntrès bien Ruby.\nLes rudiments d’un générateur de site statique\nEn vérité, un générateur de site statique c'est un concept très simple. Les\ningrédients clefs d’un générateur de site statique sont typiquement :\n\nUn (ou des) langage(s) de gabarit pour créer les modèles de pages\/articles,\nUn langage de balisage léger (en général Markdown) pour rédiger le contenu,\nUn langage de balisage structurel (souvent YAML) pour définir la configuration\net les métadonnées (par exemple\n\"front matter\"),\nUn ensemble de règles et de structure pour organiser et nommer les fichiers\nqui seront exportés\/compilés, les fichiers qui ne le seront pas et comment ces\nfichiers seront traités (par exemple préfixer un fichier ou un fichier avec un\ntiret bas(_) signifie qu'il ne sera pas recopié avec les fichiers du site\nfinal ou encore tous les articles vont dans un dossier posts),\nUn moyen de compiler les modèles et le balisage en HTML (le support pour des\npréprocesseurs CSS ou JavaScript est également fréquemment inclus),\nUn serveur local pour tester.\n\nC’est tout. Si vous vous dites \"Hé… mais je pourrais en développer un !\" vous\navez sûrement raison. Les choses se compliquent quand vous commencez à ajouter\ndes fonctionnalités, comme la plupart des générateurs de site statique le font.\nRegardons donc maintenant comment Harp gère tout cela.\nAllons au cœur du problème\nVoyons comment Harp fait pour gérer les ingrédients clefs décris ci-dessus. Harp\noffre plus que cet ensemble de fonctionnalités, mais pour l’examen qui nous\nintéresse, nous nous limiterons à ces éléments.\nPremièrement, parlons des rudiments de Harp.\nLes rudiments de Harp\nHarp supporte Jade et EJS\n(pour les modèles) et Markdown comme langage de balisage léger (pour le\ncontenu). Remarquez que bien que Jade s’appelle maintenant Pug, Harp n'a pas\nofficiellement mis à jour sa documentation ou son code, donc nous parlerons\nencore de Jade dans cet article. Harp offre aussi le support pour d’autres\npréprocesseurs comme Less, Sass et Stylus pour CSS et CoffeeScript pour\nJavaScript.\nPar défaut, Harp n'a pas trop besoin de configuration ou de métadonnées. Il tend\nà favoriser\nune convention plutôt que de la configuration.\nToutefois, il permet de configurer ou d’ajouter des métadonnées à l’aide de\nfichiers JSON. Il se distingue de beaucoup d’autres générateurs de site statique,\ncar le fichier de métadonnées est stocké en dehors du fichier le concernant dans\nun fichier _data.json.\nBien qu'il soit configurable jusqu'à un certain point, Harp a établi des règles\nstrictes sur la manière de structurer les fichiers. Typiquement, comme dans\nbeaucoup d’applications, les fichiers qui sont servis sont stockés dans un\ndossier public. Et tout fichier ou dossier préfixé d’un tiret bas(_) ne sera\npas servi.\nEnfin, Harp intègre un serveur web local basique de test qui offre quelques\noptions de configuration. Et bien entendu, Harp va compresser les fichiers HTML,\nCSS et JavaScript finaux pour le déploiement.\nRegardons donc le code source de Harp\nComme l’essentiel de ce qui fait un générateur de site statique ce sont les\nrègles et les conventions, le code s'occupe principalement de servir et de\ncompiler (en grande partie). Fouillons tout ça.\nLa fonction serveur\nDans Harp, servir votre projet se fait généralement en lançant la commande\nharp server depuis votre terminal. Regardons le\ncode for cette fonction:\nexports.server = function (dirPath, options, callback) {\n  var app = connect();\n  app.use(middleware.regProjectFinder(dirPath));\n  app.use(middleware.setup);\n  app.use(middleware.basicAuth);\n  app.use(middleware.underscore);\n  app.use(middleware.mwl);\n  app.use(middleware.static);\n  app.use(middleware.poly);\n  app.use(middleware.process);\n  app.use(middleware.fallback);\n\n  return app.listen(options.port || 9966, options.ip, function () {\n    app.projectPath = dirPath;\n    callback.apply(app, arguments);\n  });\n};\nBien que la fonction ait l’air simple, il y a manifestement un paquet de choses\nqui se passent dans\nmiddleware et\nqui n'est pas visible ici.\nLe reste de cette fonction lance un serveur avec les options spécifiées (s'il y\nen a). Ces options comprennent un port, une adresse IP à laquelle se connecter\net un répertoire. Par défaut le port est le 9000 (et pas le 9966 comme pourrait\nle laisser penser le code), le répertoire est le répertoire courant (celui dans\nlequel Harp est lancé) et l’adresse IP est 0.0.0.0.\nCes options par défaut sont détaillées dans le\ncode source de l’exécutable en ligne de commande.\nLa fonction de compilation\nRestons dans le fichier\nindex.js et jetons\nmaintenant un coup d’œil à la fonction suivante compile.\nexports.compile = function (projectPath, outputPath, callback) {\n  \/**\n   * Both projectPath and outputPath are optional\n   *\/\n\n  if (!callback) {\n    callback = outputPath;\n    outputPath = \"www\";\n  }\n\n  if (!outputPath) {\n    outputPath = \"www\";\n  }\n\n  \/**\n   * Setup all the paths and collect all the data\n   *\/\n\n  try {\n    outputPath = path.resolve(projectPath, outputPath);\n    var setup = helpers.setup(projectPath, \"production\");\n    var terra = terraform.root(setup.publicPath, setup.config.globals);\n  } catch (err) {\n    return callback(err);\n  }\n\n  \/**\n   * Protect the user (as much as possible) from compiling up the tree\n   * resulting in the project deleting its own source code.\n   *\/\n\n  if (!helpers.willAllow(projectPath, outputPath)) {\n    return callback({\n      type: \"Invalid Output Path\",\n      message:\n        \"Output path cannot be greater then one level up from project path and must be in directory starting with `_` (underscore).\",\n      projectPath: projectPath,\n      outputPath: outputPath,\n    });\n  }\n\n  \/**\n   * Compile and save file\n   *\/\n\n  var compileFile = function (file, done) {\n    process.nextTick(function () {\n      terra.render(file, function (error, body) {\n        if (error) {\n          done(error);\n        } else {\n          if (body) {\n            var dest = path.resolve(\n              outputPath,\n              terraform.helpers.outputPath(file)\n            );\n            fs.mkdirp(path.dirname(dest), function (err) {\n              fs.writeFile(dest, body, done);\n            });\n          } else {\n            done();\n          }\n        }\n      });\n    });\n  };\n\n  \/**\n   * Copy File\n   *\n   * TODO: reference ignore extensions from a terraform helper.\n   *\/\n  var copyFile = function (file, done) {\n    var ext = path.extname(file);\n    if (\n      !terraform.helpers.shouldIgnore(file) &amp;&amp;\n      [\n        \".jade\",\n        \".ejs\",\n        \".md\",\n        \".styl\",\n        \".less\",\n        \".scss\",\n        \".sass\",\n        \".coffee\",\n      ].indexOf(ext) === -1\n    ) {\n      var localPath = path.resolve(outputPath, file);\n      fs.mkdirp(path.dirname(localPath), function (err) {\n        fs.copy(path.resolve(setup.publicPath, file), localPath, done);\n      });\n    } else {\n      done();\n    }\n  };\n\n  \/**\n   * Scan dir, Compile Less and Jade, Copy the others\n   *\/\n\n  helpers.prime(outputPath, { ignore: projectPath }, function (err) {\n    if (err) console.log(err);\n\n    helpers.ls(setup.publicPath, function (err, results) {\n      async.each(results, compileFile, function (err) {\n        if (err) {\n          callback(err);\n        } else {\n          async.each(results, copyFile, function (err) {\n            setup.config[\"harp_version\"] = pkg.version;\n            delete setup.config.globals;\n            callback(null, setup.config);\n          });\n        }\n      });\n    });\n  });\n};\nLa première portion de code définit le chemin de destination passé en argument\nde l’appel à harp compile via la ligne de commande\n(le source est ici). Par\ndéfaut, comme vous pouvez le voir c'est www. callback est une fonction de\ncallback passée en argument de la ligne de commande qui n'est pas configurable.\nLa portion suivante commence par appeler la fonction setup du\nmodule helpers.\nPour faire court, nous n'irons pas examiner le code de la fonction (libre à vous\nd’aller regarder par vous-même), mais en gros ça lit la configuration du site\n(C’est-à-dire le fichier harp.json).\nVous aurez peut-être remarqué un appel à un truc qui s'appelle terraform. Cela\nrevient ensuite de nouveau au sein de cette fonction.\nTerraform est en fait projet séparé dont\nHarp dépend pour la gestion du\ntraitement des assets.\nC’est lors du traitement des assets qu'est fait tout le difficile travail de\ncompilation et de génération du site final (nous irons jeter un œil au code de\nTerraform sous peu).\nLa portion de code suivante, comme elle l’indique, essaie de vous empêcher de\ndéfinir un dossier de destination qui écraserait votre code source (ce qui\nserait malheureux puisque vous perdriez tout votre travail depuis votre dernier\ncommit).\nLes fonctions compileFile et copyFile parlent d’elles-mêmes. La fonction\ncompileFile se repose sur Terraform pour effectuer la compilation. Ces deux\nfonctions permettent à la fonction prime qui s'aide de la fonction (fs) pour\nparcourir les dossiers, compiler ou copier les fichiers nécessaires pendant le\nprocessus.\nTerraform\nComme je l’ai dit, Terraform fait le sale travail pour compiler les fichiers\nJade, Markdown, Sass et CoffeeScript en HTML, CSS et JavaScript (et pour\nassembler ces pièces comme voulu par Harp). Terraform est constitué d’un nombre\nde fichiers qui définissent ces processeurs pour JavaScript, CSS\/feuilles de\nstyle et modèles (qui ici comprennent Markdown).\n\n\n\n\n\nDans chacun de ces dossiers se trouve un dossier processors qui renferme le\ncode pour chacun des processeurs spécifiques que Terraform (C’est-à-dire Harp)\nsupporte. Par exemple, dans le dossier des modèles se trouvent les fichiers qui\npermettent de compiler les fichiers EJS, Jade, and Markdown.\n\n\n\n\n\nJe ne vais pas aller creuser le code de chacun d’entre eux, mais pour la\nplupart, ils se reposent sur des modules npm externes qui gèrent le processeur\nsupporté. Par exemple, le support de Markdown dépend de\nMarked.\nLe cœur de la logique de Terraform se trouve dans sa fonction render.\n\/**\n        * Render\n        *\n        * This is the main method to to render a view. This function is\n        * responsible to for figuring out the layout to use and sets the\n        * `current` object.\n        *\n        *\/\n\n    render: function(filePath, locals, callback){\n\n        \/\/ get rid of leading slash (windows)\n        filePath = filePath.replace(\/^\\\\\/g, '')\n\n        \/\/ locals are optional\n        if(!callback){\n        callback = locals\n        locals   = {}\n        }\n\n\n        \/**\n        * We ignore files that start with underscore\n        *\/\n\n        if(helpers.shouldIgnore(filePath)) return callback(null, null)\n\n\n        \/**\n        * If template file we need to set current and other locals\n        *\/\n\n        if(helpers.isTemplate(filePath)) {\n\n        \/**\n            * Current\n            *\/\n        locals._ = lodash\n        locals.current = helpers.getCurrent(filePath)\n\n\n        \/**\n            * Layout Priority:\n            *\n            *    1. passed into partial() function.\n            *    2. in `_data.json` file.\n            *    3. default layout.\n            *    4. no layout\n            *\/\n\n        \/\/ 1. check for layout passed in\n        if(!locals.hasOwnProperty('layout')){\n\n            \/\/ 2. _data.json layout\n            \/\/ TODO: Change this lookup relative to path.\n            var templateLocals = helpers.walkData(locals.current.path, data)\n\n            if(templateLocals &amp;&amp; templateLocals.hasOwnProperty('layout')){\n            if(templateLocals['layout'] === false){\n                locals['layout'] = null\n            } else if(templateLocals['layout'] !== true){\n\n                \/\/ relative path\n                var dirname = path.dirname(filePath)\n                var layoutPriorityList = helpers.buildPriorityList(path.join(dirname, templateLocals['layout'] || \"\"))\n\n                \/\/ absolute path (fallback)\n                layoutPriorityList.push(templateLocals['layout'])\n\n                \/\/ return first existing file\n                \/\/ TODO: Throw error if null\n                locals['layout'] = helpers.findFirstFile(root, layoutPriorityList)\n\n            }\n            }\n\n            \/\/ 3. default _layout file\n            if(!locals.hasOwnProperty('layout')){\n            locals['layout'] = helpers.findDefaultLayout(root, filePath)\n            }\n\n            \/\/ 4. no layout (do nothing)\n        }\n\n        \/**\n            * TODO: understand again why we are doing this.\n            *\/\n\n        try{\n            var error  = null\n            var output = template(root, templateObject).partial(filePath, locals)\n        }catch(e){\n            var error  = e\n            var output = null\n        }finally{\n            callback(error, output)\n        }\n\n        }else if(helpers.isStylesheet(filePath)){\n        stylesheet(root, filePath, callback)\n        }else if(helpers.isJavaScript(filePath)){\n        javascript(root, filePath, callback)\n        }else{\n        callback(null, null)\n        }\n\n    }\n(Si vous avez bien lu l’intégralité de ce code, vous aurez peut-être relevé\nquelques TODO et un commentaire assez drôle \"comprendre à nouveau pourquoi nous\nfaisons cela\". C’est ça le code dans la vraie vie!)\nLa grande partie du code dans la fonction render s'occupe de la gestion des\nmodèles. Des trucs comme CoffeeScript et Sass génèrent fondamentalement un seul\nfichier. Par exemple, style.scss donnera un fichier style.css. Même s'il y a\ndes includes c'est géré par le préprocesseur. La toute fin de la fonction\nrender s'occupe de ces types de fichiers.\nLes modèles dans Harp, à l’opposé,\nsont imbriqués les uns dans les autres de différentes manières qui peuvent aussi\ndépendre de la configuration. Par exemple, le fichier about.md peut être rendu\npar le modèle _layout.jade (ce qui, pour être précis, est défini par\nl’utilisation de != yield à l’intérieur du modèle). Toutefois, _layout.jade\npourrait également lui-même faire appel à plusieurs autres modèles en son sein\ngrâce au support des fichiers\npartiels dans Harp.\nLes fichiers partiels sont une façon de découper un modèle en plusieurs\nfichiers. Ils sont particulièrement utiles pour réutiliser du code. Par exemple,\nj'aurais pu mettre l’entête du site dans un fichier partiel. Les fichiers\npartiels sont importants pour rendre la gestion des modèles plus maintenable,\nmais ils ajoutent aussi un bon degré de complexité dans la compilation des\nmodèles. Cette complexité est gérée à l’intérieur de la fonction partial du\ntraitement des modèles.\nEnfin, nous pourrions écraser le modèle par défaut en définissant un modèle\nspécifique ou pas de modèle du tout pour un fichier particulier à l’intérieur du\nfichier de configuration _data.json. Tous ces scénarios sont gérés (et même\nnumérotés) dans la logique de la fonction render.\nC’est pas si compliqué, non ?\nPour rendre tout cela digeste, j'ai fait l’impasse sur pas mal de détails. À la\nbase, chaque générateur de site statique que j'ai utilisé (et j'en ai utilisé\nun paquet) fonctionne de\nmanière similaire : un ensemble de règles, de conventions et de configuration\nqui sont traitées à travers des compilateurs de langages variés. C’est\npeut-être pour ça qu'il y a un\nnombre ridicule de générateurs de site\nstatique dans la nature.\nCeci étant dit, je ne me lancerais pas dans le développement du mien!\nMon rapport et mon livre\nSi vous voulez apprendre comment faire des sites à l’aide d’un générateur de\nsite statique, J’ai écrit un rapport et coécrit un livre pour O'Reilly qui\npourrait vous intéresser. Mon rapport, simplement intitulé\nLes générateurs de site statique\nest gratuit et essaie d’aborder l’historique, le paysage actuel et les\nfondamentaux des générateurs de site statique.\n\n\n\n\n\nLe livre que j'ai coécrit avec Raymond Camden s'appelle Travailler avec les sites statiques et est disponible en prépublication, mais devrait bientôt être disponible en version papier.",
      "content_html": "<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\nfamiliariser avec différents générateurs de site statique, voici la traduction\nd’un <a href=\"https://css-tricks.com/really-makes-static-site-generator/\" target=\"_blank\" rel=\"noopener noreferrer\">article de Brian Rinaldi paru chez\nCSS-tricks</a>, qui\nnous entraîne cette fois-ci dans les entrailles de <a href=\"https://harpjs.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Harp</a>,\nun générateur de fichiers statiques développé en JavaScript, qui résume bien le\npérimètre fonctionnel de ces outils.</p></aside>\n<p>Je parle beaucoup des générateurs de site statique, mais je parle toujours de\ncomment <em>utiliser</em> des générateurs de site statique. Ils sont souvent perçus\ncomme une boîte noire. Je crée un modèle, j'écris un peu de Markdown et hop\nj'obtiens une page entièrement formatée en HTML. C’est magique !</p>\n<p>Mais qu'est-ce vraiment un générateur de site statique ? Qu’y a-t-il dans cette\nboîte noire ? De quelle magie Vaudou s'agit-il ?</p>\n<p>Dans cet article, nous allons examiner les différentes parties qui constituent\nun générateur de site statique. Premièrement, nous parlerons des généralités et\nensuite nous regarderons de plus près un vrai bout de code source en plongeant\nau plus profond d’<a href=\"https://harpjs.com/\" target=\"_blank\" rel=\"noopener noreferrer\">HarpJS</a>. Alors, enfilez votre casquette\nd’aventurier et partons en exploration.</p>\n<p>Pourquoi Harp ? Pour deux raisons. La première est qu'HarpJS est, de par sa\nconception, un générateur de site statique très simple. Il ne possède pas\nbeaucoup de fonctionnalités qui feraient que nous nous perdrions en route en\nvoulant explorer un générateur de site statique plus complet (comme\n<a href=\"https://jekyllrb.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Jekyll</a> par exemple). La deuxième raison à cela, bien\nplus pragmatique, est que je connais bien JavaScript alors que je ne connais pas\ntrès bien Ruby.</p>\n<h2 id=\"les-rudiments-d-un-generateur-de-site-statique\">Les rudiments d’un générateur de site statique</h2>\n<p>En vérité, un générateur de site statique c'est un concept très simple. Les\ningrédients clefs d’un générateur de site statique sont typiquement :</p>\n<ul>\n<li>Un (ou des) langage(s) de gabarit pour créer les modèles de pages/articles,</li>\n<li>Un langage de balisage léger (en général Markdown) pour rédiger le contenu,</li>\n<li>Un langage de balisage structurel (souvent YAML) pour définir la configuration\net les métadonnées (par exemple\n\"<a href=\"https://jekyllrb.com/docs/frontmatter/\" target=\"_blank\" rel=\"noopener noreferrer\">front matter</a>\"),</li>\n<li>Un ensemble de règles et de structure pour organiser et nommer les fichiers\nqui seront exportés/compilés, les fichiers qui ne le seront pas et comment ces\nfichiers seront traités (par exemple préfixer un fichier ou un fichier avec un\ntiret bas(<code>_</code>) signifie qu'il ne sera pas recopié avec les fichiers du site\nfinal ou encore tous les articles vont dans un dossier <code>posts</code>),</li>\n<li>Un moyen de compiler les modèles et le balisage en HTML (le support pour des\npréprocesseurs CSS ou JavaScript est également fréquemment inclus),</li>\n<li>Un serveur local pour tester.</li>\n</ul>\n<p>C’est tout. Si vous vous dites \"Hé… mais je pourrais en développer un !\" vous\navez sûrement raison. Les choses se compliquent quand vous commencez à ajouter\ndes fonctionnalités, comme la plupart des générateurs de site statique le font.</p>\n<p>Regardons donc maintenant comment Harp gère tout cela.</p>\n<h2 id=\"allons-au-c艙ur-du-probl猫me\">Allons au cœur du problème</h2>\n<p>Voyons comment Harp fait pour gérer les ingrédients clefs décris ci-dessus. Harp\noffre plus que cet ensemble de fonctionnalités, mais pour l’examen qui nous\nintéresse, nous nous limiterons à ces éléments.</p>\n<p>Premièrement, parlons des rudiments de Harp.</p>\n<h2 id=\"les-rudiments-de-harp\">Les rudiments de Harp</h2>\n<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>\n(pour les modèles) et Markdown comme langage de balisage léger (pour le\ncontenu). Remarquez que bien que Jade s’appelle maintenant Pug, Harp n'a pas\nofficiellement mis à jour sa documentation ou son code, donc nous parlerons\nencore de Jade dans cet article. Harp offre aussi le support pour d’autres\npréprocesseurs comme Less, Sass et Stylus pour CSS et CoffeeScript pour\nJavaScript.</p>\n<p>Par défaut, Harp n'a pas trop besoin de configuration ou de métadonnées. Il tend\nà favoriser\n<a href=\"https://harpjs.com/docs/development/rules\" target=\"_blank\" rel=\"noopener noreferrer\">une convention plutôt que de la configuration</a>.\nToutefois, il permet de configurer ou d’ajouter des métadonnées à l’aide de\nfichiers JSON. Il se distingue de beaucoup d’autres générateurs de site statique,\ncar le fichier de métadonnées est stocké en dehors du fichier le concernant dans\nun fichier <code>_data.json</code>.</p>\n<p>Bien qu'il soit configurable jusqu'à un certain point, Harp a établi des règles\nstrictes sur la manière de structurer les fichiers. Typiquement, comme dans\nbeaucoup d’applications, les fichiers qui sont servis sont stockés dans un\ndossier <code>public</code>. Et tout fichier ou dossier préfixé d’un tiret bas(<code>_</code>) ne sera\npas servi.</p>\n<p>Enfin, Harp intègre un serveur web local basique de test qui offre quelques\noptions de configuration. Et bien entendu, Harp va compresser les fichiers HTML,\nCSS et JavaScript finaux pour le déploiement.</p>\n<h3 id=\"regardons-donc-le-code-source-de-harp\">Regardons donc le code source de Harp</h3>\n<p>Comme l’essentiel de ce qui fait un générateur de site statique ce sont les\nrègles et les conventions, le code s'occupe principalement de servir et de\ncompiler (en grande partie). Fouillons tout ça.</p>\n<h3 id=\"la-fonction-serveur\">La fonction serveur</h3>\n<p>Dans Harp, servir votre projet se fait généralement en lançant la commande\n<code>harp server</code> depuis votre terminal. Regardons le\n<a href=\"https://github.com/sintaxi/harp/blob/master/lib/index.js\" target=\"_blank\" rel=\"noopener noreferrer\">code for cette fonction</a>:</p>\n<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>{\n  <span class=\"hljs-keyword\">var</span> app = connect();\n  app.use(middleware.regProjectFinder(dirPath));\n  app.use(middleware.setup);\n  app.use(middleware.basicAuth);\n  app.use(middleware.underscore);\n  app.use(middleware.mwl);\n  app.use(middleware.static);\n  app.use(middleware.poly);\n  app.use(middleware.process);\n  app.use(middleware.fallback);\n\n  <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>{\n    app.projectPath = dirPath;\n    callback.apply(app, <span class=\"hljs-built_in\">arguments</span>);\n  });\n};</code></pre>\n<p>Bien que la fonction ait l’air simple, il y a manifestement un paquet de choses\nqui se passent dans\n<a href=\"https://github.com/sintaxi/harp/blob/master/lib/middleware.js\" target=\"_blank\" rel=\"noopener noreferrer\">middleware</a> et\nqui n'est pas visible ici.</p>\n<p>Le reste de cette fonction lance un serveur avec les options spécifiées (s'il y\nen a). Ces options comprennent un port, une adresse IP à laquelle se connecter\net un répertoire. Par défaut le port est le 9000 (et pas le 9966 comme pourrait\nle laisser penser le code), le répertoire est le répertoire courant (celui dans\nlequel Harp est lancé) et l’adresse IP est <code>0.0.0.0</code>.</p>\n<p>Ces options par défaut sont détaillées dans le\n<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>\n<h3 id=\"la-fonction-de-compilation\">La fonction de compilation</h3>\n<p>Restons dans le fichier\n<a href=\"https://github.com/sintaxi/harp/blob/master/lib/index.js\" target=\"_blank\" rel=\"noopener noreferrer\">index.js</a> et jetons\nmaintenant un coup d’œil à la fonction suivante <code>compile</code>.</p>\n<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>{\n  <span class=\"hljs-comment\">/**\n   * Both projectPath and outputPath are optional\n   */</span>\n\n  <span class=\"hljs-keyword\">if</span> (!callback) {\n    callback = outputPath;\n    outputPath = <span class=\"hljs-string\">\"www\"</span>;\n  }\n\n  <span class=\"hljs-keyword\">if</span> (!outputPath) {\n    outputPath = <span class=\"hljs-string\">\"www\"</span>;\n  }\n\n  <span class=\"hljs-comment\">/**\n   * Setup all the paths and collect all the data\n   */</span>\n\n  <span class=\"hljs-keyword\">try</span> {\n    outputPath = path.resolve(projectPath, outputPath);\n    <span class=\"hljs-keyword\">var</span> setup = helpers.setup(projectPath, <span class=\"hljs-string\">\"production\"</span>);\n    <span class=\"hljs-keyword\">var</span> terra = terraform.root(setup.publicPath, setup.config.globals);\n  } <span class=\"hljs-keyword\">catch</span> (err) {\n    <span class=\"hljs-keyword\">return</span> callback(err);\n  }\n\n  <span class=\"hljs-comment\">/**\n   * Protect the user (as much as possible) from compiling up the tree\n   * resulting in the project deleting its own source code.\n   */</span>\n\n  <span class=\"hljs-keyword\">if</span> (!helpers.willAllow(projectPath, outputPath)) {\n    <span class=\"hljs-keyword\">return</span> callback({\n      <span class=\"hljs-attr\">type</span>: <span class=\"hljs-string\">\"Invalid Output Path\"</span>,\n      <span class=\"hljs-attr\">message</span>:\n        <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>,\n      <span class=\"hljs-attr\">projectPath</span>: projectPath,\n      <span class=\"hljs-attr\">outputPath</span>: outputPath,\n    });\n  }\n\n  <span class=\"hljs-comment\">/**\n   * Compile and save file\n   */</span>\n\n  <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>{\n    process.nextTick(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\"></span>) </span>{\n      terra.render(file, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">error, body</span>) </span>{\n        <span class=\"hljs-keyword\">if</span> (error) {\n          done(error);\n        } <span class=\"hljs-keyword\">else</span> {\n          <span class=\"hljs-keyword\">if</span> (body) {\n            <span class=\"hljs-keyword\">var</span> dest = path.resolve(\n              outputPath,\n              terraform.helpers.outputPath(file)\n            );\n            fs.mkdirp(path.dirname(dest), <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">err</span>) </span>{\n              fs.writeFile(dest, body, done);\n            });\n          } <span class=\"hljs-keyword\">else</span> {\n            done();\n          }\n        }\n      });\n    });\n  };\n\n  <span class=\"hljs-comment\">/**\n   * Copy File\n   *\n   * <span class=\"hljs-doctag\">TODO:</span> reference ignore extensions from a terraform helper.\n   */</span>\n  <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>{\n    <span class=\"hljs-keyword\">var</span> ext = path.extname(file);\n    <span class=\"hljs-keyword\">if</span> (\n      !terraform.helpers.shouldIgnore(file) &amp;&amp;\n      [\n        <span class=\"hljs-string\">\".jade\"</span>,\n        <span class=\"hljs-string\">\".ejs\"</span>,\n        <span class=\"hljs-string\">\".md\"</span>,\n        <span class=\"hljs-string\">\".styl\"</span>,\n        <span class=\"hljs-string\">\".less\"</span>,\n        <span class=\"hljs-string\">\".scss\"</span>,\n        <span class=\"hljs-string\">\".sass\"</span>,\n        <span class=\"hljs-string\">\".coffee\"</span>,\n      ].indexOf(ext) === <span class=\"hljs-number\">-1</span>\n    ) {\n      <span class=\"hljs-keyword\">var</span> localPath = path.resolve(outputPath, file);\n      fs.mkdirp(path.dirname(localPath), <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">err</span>) </span>{\n        fs.copy(path.resolve(setup.publicPath, file), localPath, done);\n      });\n    } <span class=\"hljs-keyword\">else</span> {\n      done();\n    }\n  };\n\n  <span class=\"hljs-comment\">/**\n   * Scan dir, Compile Less and Jade, Copy the others\n   */</span>\n\n  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>{\n    <span class=\"hljs-keyword\">if</span> (err) <span class=\"hljs-built_in\">console</span>.log(err);\n\n    helpers.ls(setup.publicPath, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function</span> (<span class=\"hljs-params\">err, results</span>) </span>{\n      <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>{\n        <span class=\"hljs-keyword\">if</span> (err) {\n          callback(err);\n        } <span class=\"hljs-keyword\">else</span> {\n          <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>{\n            setup.config[<span class=\"hljs-string\">\"harp_version\"</span>] = pkg.version;\n            <span class=\"hljs-keyword\">delete</span> setup.config.globals;\n            callback(<span class=\"hljs-literal\">null</span>, setup.config);\n          });\n        }\n      });\n    });\n  });\n};</code></pre>\n<p>La première portion de code définit le chemin de destination passé en argument\nde l’appel à <code>harp compile</code> via la ligne de commande\n(<a href=\"https://github.com/sintaxi/harp/blob/master/bin/harp\" target=\"_blank\" rel=\"noopener noreferrer\">le source est ici</a>). Par\ndéfaut, comme vous pouvez le voir c'est <code>www</code>. <code>callback</code> est une fonction de\ncallback passée en argument de la ligne de commande qui n'est pas configurable.</p>\n<p>La portion suivante commence par appeler la fonction <code>setup</code> du\n<a href=\"https://github.com/sintaxi/harp/blob/master/lib/helpers.js\" target=\"_blank\" rel=\"noopener noreferrer\">module helpers</a>.\nPour faire court, nous n'irons pas examiner le code de la fonction (libre à vous\nd’aller regarder par vous-même), mais en gros ça lit la configuration du site\n(C’est-à-dire le fichier <code>harp.json</code>).</p>\n<p>Vous aurez peut-être remarqué un appel à un truc qui s'appelle <code>terraform</code>. Cela\nrevient ensuite de nouveau au sein de cette fonction.\n<a href=\"https://github.com/sintaxi/terraform\" target=\"_blank\" rel=\"noopener noreferrer\">Terraform</a> est en fait projet séparé dont\nHarp dépend pour la gestion du\n<a href=\"https://launchschool.com/blog/rails-asset-pipeline-best-practices\" target=\"_blank\" rel=\"noopener noreferrer\">traitement des assets</a>.\nC’est lors du traitement des assets qu'est fait tout le difficile travail de\ncompilation et de génération du site final (nous irons jeter un œil au code de\nTerraform sous peu).</p>\n<p>La portion de code suivante, comme elle l’indique, essaie de vous empêcher de\ndéfinir un dossier de destination qui écraserait votre code source (ce qui\nserait malheureux puisque vous perdriez tout votre travail depuis votre dernier\ncommit).</p>\n<p>Les fonctions <code>compileFile</code> et <code>copyFile</code> parlent d’elles-mêmes. La fonction\n<code>compileFile</code> se repose sur Terraform pour effectuer la compilation. Ces deux\nfonctions permettent à la fonction <code>prime</code> qui s'aide de la fonction (<code>fs</code>) pour\nparcourir les dossiers, compiler ou copier les fichiers nécessaires pendant le\nprocessus.</p>\n<h3 id=\"terraform\">Terraform</h3>\n<p>Comme je l’ai dit, Terraform fait le sale travail pour compiler les fichiers\nJade, Markdown, Sass et CoffeeScript en HTML, CSS et JavaScript (et pour\nassembler ces pièces comme voulu par Harp). Terraform est constitué d’un nombre\nde fichiers qui définissent ces processeurs pour JavaScript, CSS/feuilles de\nstyle et modèles (qui ici comprennent Markdown).</p>\n<picture>\n<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\">\n<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\">\n<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;\">\n</picture>\n<p>Dans chacun de ces dossiers se trouve un dossier <code>processors</code> qui renferme le\ncode pour chacun des processeurs spécifiques que Terraform (C’est-à-dire Harp)\nsupporte. Par exemple, dans le dossier des modèles se trouvent les fichiers qui\npermettent de compiler les fichiers EJS, Jade, and Markdown.</p>\n<picture>\n<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\">\n<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\">\n<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,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);background-repeat:no-repeat;background-position:center;background-size:cover;\">\n</picture>\n<p>Je ne vais pas aller creuser le code de chacun d’entre eux, mais pour la\nplupart, ils se reposent sur des modules npm externes qui gèrent le processeur\nsupporté. Par exemple, le support de Markdown dépend de\n<a href=\"https://www.npmjs.com/package/marked\" target=\"_blank\" rel=\"noopener noreferrer\">Marked</a>.</p>\n<p>Le cœur de la logique de Terraform se trouve dans sa fonction <code>render</code>.</p>\n<pre><code class=\"language-js hljs javascript\"><span class=\"hljs-comment\">/**\n        * Render\n        *\n        * This is the main method to to render a view. This function is\n        * responsible to for figuring out the layout to use and sets the\n        * `current` object.\n        *\n        */</span>\n\n    <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>{\n\n        <span class=\"hljs-comment\">// get rid of leading slash (windows)</span>\n        filePath = filePath.replace(<span class=\"hljs-regexp\">/^\\\\/g</span>, <span class=\"hljs-string\">''</span>)\n\n        <span class=\"hljs-comment\">// locals are optional</span>\n        <span class=\"hljs-keyword\">if</span>(!callback){\n        callback = locals\n        locals   = {}\n        }\n\n\n        <span class=\"hljs-comment\">/**\n        * We ignore files that start with underscore\n        */</span>\n\n        <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>)\n\n\n        <span class=\"hljs-comment\">/**\n        * If template file we need to set current and other locals\n        */</span>\n\n        <span class=\"hljs-keyword\">if</span>(helpers.isTemplate(filePath)) {\n\n        <span class=\"hljs-comment\">/**\n            * Current\n            */</span>\n        locals._ = lodash\n        locals.current = helpers.getCurrent(filePath)\n\n\n        <span class=\"hljs-comment\">/**\n            * Layout Priority:\n            *\n            *    1. passed into partial() function.\n            *    2. in `_data.json` file.\n            *    3. default layout.\n            *    4. no layout\n            */</span>\n\n        <span class=\"hljs-comment\">// 1. check for layout passed in</span>\n        <span class=\"hljs-keyword\">if</span>(!locals.hasOwnProperty(<span class=\"hljs-string\">'layout'</span>)){\n\n            <span class=\"hljs-comment\">// 2. _data.json layout</span>\n            <span class=\"hljs-comment\">// <span class=\"hljs-doctag\">TODO:</span> Change this lookup relative to path.</span>\n            <span class=\"hljs-keyword\">var</span> templateLocals = helpers.walkData(locals.current.path, data)\n\n            <span class=\"hljs-keyword\">if</span>(templateLocals &amp;&amp; templateLocals.hasOwnProperty(<span class=\"hljs-string\">'layout'</span>)){\n            <span class=\"hljs-keyword\">if</span>(templateLocals[<span class=\"hljs-string\">'layout'</span>] === <span class=\"hljs-literal\">false</span>){\n                locals[<span class=\"hljs-string\">'layout'</span>] = <span class=\"hljs-literal\">null</span>\n            } <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>){\n\n                <span class=\"hljs-comment\">// relative path</span>\n                <span class=\"hljs-keyword\">var</span> dirname = path.dirname(filePath)\n                <span class=\"hljs-keyword\">var</span> layoutPriorityList = helpers.buildPriorityList(path.join(dirname, templateLocals[<span class=\"hljs-string\">'layout'</span>] || <span class=\"hljs-string\">\"\"</span>))\n\n                <span class=\"hljs-comment\">// absolute path (fallback)</span>\n                layoutPriorityList.push(templateLocals[<span class=\"hljs-string\">'layout'</span>])\n\n                <span class=\"hljs-comment\">// return first existing file</span>\n                <span class=\"hljs-comment\">// <span class=\"hljs-doctag\">TODO:</span> Throw error if null</span>\n                locals[<span class=\"hljs-string\">'layout'</span>] = helpers.findFirstFile(root, layoutPriorityList)\n\n            }\n            }\n\n            <span class=\"hljs-comment\">// 3. default _layout file</span>\n            <span class=\"hljs-keyword\">if</span>(!locals.hasOwnProperty(<span class=\"hljs-string\">'layout'</span>)){\n            locals[<span class=\"hljs-string\">'layout'</span>] = helpers.findDefaultLayout(root, filePath)\n            }\n\n            <span class=\"hljs-comment\">// 4. no layout (do nothing)</span>\n        }\n\n        <span class=\"hljs-comment\">/**\n            * <span class=\"hljs-doctag\">TODO:</span> understand again why we are doing this.\n            */</span>\n\n        <span class=\"hljs-keyword\">try</span>{\n            <span class=\"hljs-keyword\">var</span> error  = <span class=\"hljs-literal\">null</span>\n            <span class=\"hljs-keyword\">var</span> output = template(root, templateObject).partial(filePath, locals)\n        }<span class=\"hljs-keyword\">catch</span>(e){\n            <span class=\"hljs-keyword\">var</span> error  = e\n            <span class=\"hljs-keyword\">var</span> output = <span class=\"hljs-literal\">null</span>\n        }<span class=\"hljs-keyword\">finally</span>{\n            callback(error, output)\n        }\n\n        }<span class=\"hljs-keyword\">else</span> <span class=\"hljs-keyword\">if</span>(helpers.isStylesheet(filePath)){\n        stylesheet(root, filePath, callback)\n        }<span class=\"hljs-keyword\">else</span> <span class=\"hljs-keyword\">if</span>(helpers.isJavaScript(filePath)){\n        javascript(root, filePath, callback)\n        }<span class=\"hljs-keyword\">else</span>{\n        callback(<span class=\"hljs-literal\">null</span>, <span class=\"hljs-literal\">null</span>)\n        }\n\n    }</code></pre>\n<p>(Si vous avez bien lu l’intégralité de ce code, vous aurez peut-être relevé\nquelques TODO et un commentaire assez drôle \"comprendre à nouveau pourquoi nous\nfaisons cela\". C’est ça le code dans la vraie vie!)</p>\n<p>La grande partie du code dans la fonction <code>render</code> s'occupe de la gestion des\nmodèles. Des trucs comme CoffeeScript et Sass génèrent fondamentalement un seul\nfichier. Par exemple, <code>style.scss</code> donnera un fichier <code>style.css</code>. Même s'il y a\ndes includes c'est géré par le préprocesseur. La toute fin de la fonction\n<code>render</code> s'occupe de ces types de fichiers.</p>\n<p><a href=\"https://harpjs.com/docs/development/layout\" target=\"_blank\" rel=\"noopener noreferrer\">Les modèles dans Harp</a>, à l’opposé,\nsont imbriqués les uns dans les autres de différentes manières qui peuvent aussi\ndépendre de la configuration. Par exemple, le fichier <code>about.md</code> peut être rendu\npar le modèle <code>_layout.jade</code> (ce qui, pour être précis, est défini par\nl’utilisation de <code>!= yield</code> à l’intérieur du modèle). Toutefois, <code>_layout.jade</code>\npourrait également lui-même faire appel à plusieurs autres modèles en son sein\ngrâce au support des fichiers\n<a href=\"https://harpjs.com/docs/development/partial\" target=\"_blank\" rel=\"noopener noreferrer\">partiels</a> dans Harp.</p>\n<p>Les fichiers partiels sont une façon de découper un modèle en plusieurs\nfichiers. Ils sont particulièrement utiles pour réutiliser du code. Par exemple,\nj'aurais pu mettre l’entête du site dans un fichier partiel. Les fichiers\npartiels sont importants pour rendre la gestion des modèles plus maintenable,\nmais ils ajoutent aussi un bon degré de complexité dans la compilation des\nmodèles. Cette complexité est gérée à l’intérieur de la fonction <code>partial</code> du\n<a href=\"https://github.com/sintaxi/terraform/blob/master/lib/template/index.js\" target=\"_blank\" rel=\"noopener noreferrer\">traitement des modèles</a>.</p>\n<p>Enfin, nous pourrions écraser le modèle par défaut en définissant un modèle\nspécifique ou pas de modèle du tout pour un fichier particulier à l’intérieur du\nfichier de configuration <code>_data.json</code>. Tous ces scénarios sont gérés (et même\nnumérotés) dans la logique de la fonction <code>render</code>.</p>\n<h2 id=\"c-est-pas-si-complique-non\">C’est pas si compliqué, non ?</h2>\n<p>Pour rendre tout cela digeste, j'ai fait l’impasse sur pas mal de détails. À la\nbase, chaque générateur de site statique que j'ai utilisé (et j'en ai utilisé\n<a href=\"https://github.com/remotesynth/Static-Site-Samples\" target=\"_blank\" rel=\"noopener noreferrer\">un paquet</a>) fonctionne de\nmanière similaire : un ensemble de règles, de conventions et de configuration\nqui sont traitées à travers des compilateurs de langages variés. C’est\npeut-être pour ça qu'il y a un\n<a href=\"https://staticsitegenerators.net/\" target=\"_blank\" rel=\"noopener noreferrer\">nombre ridicule</a> de générateurs de site\nstatique dans la nature.</p>\n<p>Ceci étant dit, je ne me lancerais pas dans le développement du mien!</p>\n<h2 id=\"mon-rapport-et-mon-livre\">Mon rapport et mon livre</h2>\n<p>Si vous voulez apprendre comment faire des sites à l’aide d’un générateur de\nsite statique, J’ai écrit un rapport et coécrit un livre pour O'Reilly qui\npourrait vous intéresser. Mon rapport, simplement intitulé\n<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>\nest gratuit et essaie d’aborder l’historique, le paysage actuel et les\nfondamentaux des générateurs de site statique.</p>\n<picture>\n<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\">\n<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\">\n<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\">\n</picture>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/01/23/produire-des-livres-avec-le-statique/",
      "url": "https://jamstatic.fr/2017/01/23/produire-des-livres-avec-le-statique/",
      "title": "Publier des livres avec un générateur de site statique",
      "summary": "Le web au secours de la modernisation du procéde de publication de livres numériques et papier.",
      "date_published": "2017-01-23T15:37:00+00:00","content_text": "Suite à la parution du procédé de publication numérique basé sur Git et Middleman d’un éditeur, Antoine Fauchié est allé poser quelques questions à Eric Gardner, développeur et designer au sein de l’équipe d’édition numérique de The Getty, un campus culturel et de recherche situé à Los Angeles.\nComment 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 ?\nLorsque 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.\nJe 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.\nC’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.\nNotre deuxième préoccupation était la dépendance à des logiciels propriétaires.\nDes 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.\nEnfin, 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é.\nComment votre équipe s'est appropriée ce nouveau workflow ? L'appropriation a-t-elle été facile ?\nIl 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.\n\n\n\n\n\n\nDigital Publishing at the Getty\n\nEst-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) ?\nJe 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 un article là-dessus 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 Emacs1, la possibilité de spécifier ce qu'on veut dire vraiment avec des langages de balisage simples comme Markdown ou Asciidoc, 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 sens et ce dans plus d’un contexte de présentation, c'est plutôt ça la voie à suivre.\nEst-ce que ce nouveau process augmente la qualité des publications de The Getty ?\nHaha, 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.\nEst-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 ?\nCela 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.\nQuelles sont les contraintes auxquelles vous vous heurtez encore aujourd’hui ? Quel serait votre workflow idéal ?\nTout à 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 librairies2.\nEst-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 ?\nDes 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\" 😉\n\n\n\n\nNdT: Des éditeurs comme Sublime ou Atom sont aussi puissants et encore plus accessibles.&#160;&#8617;\n\n\nNdT: Le projet GitBook adopte cette démarche.&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<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>\n<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>\n<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>\nC’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>\n<p>Notre deuxième préoccupation était la dépendance à des logiciels propriétaires.<br>\nDes 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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"Digital Publishing at the Getty\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Digital Publishing at the Getty</figcaption>\n</figure>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n<li id=\"fn:2\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "antoine"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2017/01/17/comment-fonctionne-jekyll/",
      "url": "https://jamstatic.fr/2017/01/17/comment-fonctionne-jekyll/",
      "title": "Comment marche Jekyll ?",
      "summary": "À partir du code source, nous pouvons mieux comprendre le process de génération au cœur de Jekyll.",
      "date_published": "2017-01-17T00:00:00+00:00","content_text": "Si vous suivez ce blog, vous savez déjà que Jekyll est un\ngénérateur de site statique développé en Ruby. Jack Phelan a décidé d’aller\njeter un œil dans le moteur de Jekyll histoire de mieux comprendre comment sont\ntraités les différents types de fichiers qui sont passés en entrée. Nous\ntraduisons\nson article\nafin de vous inciter à plonger un peu dans le code de Jekyll et prendre\nconnaissance des concepts fondamentaux de ce générateur. Nous espérons que cela\nvous permettra de mieux appréhender la philosophie de Jekyll ou que cela vous\nsera utile si vous songez à développer un plugin.\n\n\n.tabs {\n    position: relative;\n    margin: 25px 0;\n}\n.tab {\n    display: inline-block;\n    padding: 8px 18px;\n    border: 1px solid #4A21CC;\n    margin: 0;\n    margin-left: -1px;\n    position: relative;\n    left: 4px;\n    bottom: -1px;\n    border-top-left-radius: 3px;\n    border-top-right-radius: 3px;\n    cursor: pointer;\n}\n.tab.tab-selected {\n    background-color: #4A21CC;\n    color: white;\n    border-bottom: 1px solid #4A21CC;\n}\n\ntable {\n  font-size: 0.775em;\n  margin: 2em calc(50% - 50vw);\n}\n\ntable tr,\ntable td,\ntable th {\n  border: 1px solid rgba(35,27,48,.2);\n  border-collapse: collapse;\n  padding: .5em;\n}\n\nth, tr td:first-child {\n  font-weight: bold;\n  background-color: rgba(74,33,294,.75);\n  color: white;\n}\n\n.copied {\n  background-color: rgba(125,172,255,.2);\n}\n.transformed {\n  background-color: rgba(204,204,32,.2);\n}\n.post-transformed {\n  background-color: rgba(64,204,32,.2);\n}\n\n\n\n    function activateTab(tab) {\n        var $tabs = $(tab).closest('.tabs')\n        var index = $(tab).index()\n        var $tabAreas = $tabs.find('.tab-content')\n        var $toShow = $tabAreas.eq(index);\n\n        $tabs.find('.tab-selected').removeClass('tab-selected');\n        $(tab).addClass('tab-selected');\n\n        $tabAreas.hide()\n        $toShow.show()\n    }\n    $(() => {\n      $(\".tab\").click((e) => activateTab(e.target))\n      $('.tabs').each((i, tabs) => activateTab($(tabs).find('.tab').first().get()));\n    })\n\nJekyll peut paraître un peu déroutant au début. En effet\nJekyll ne fait pas grand-chose à vos fichiers, si ce n'est qu'il les classifie\nde différentes façons.\nJekyll va soit copier, soit omettre, soit transformer les fichiers du répertoire\nsource dans le répertoire de destination[^1]. Lorsque Jekyll transforme vos\nfichiers, c'est toujours de cette manière, si ce n'est que la deuxième étape\npeut être potentiellement sautée.[^2]\nSi un fichier commence par une entête\nYAML Front Matter Jekyll va appliquer\nles transformations suivante au fichier:\n\nInterprétation du code Liquid : Le contenu du fichier est d’abord\nparcouru par le parser de Liquid, les\nvariables comme site ou page auxquelles le modèle Liquid veut accéder\nsont alors interprétées.\nConversion du contenu : en fonction de l’extension de fichier, Jekyll\nfait appel à un convertisseur dédié, par example Kramdown pour les fichiers\n.md ou .markdown, qui est chargé de convertir le résultat obtenu après\nl’étape 1.\nParsing du modèle: Le résultat de cette conversion est alors transmis\ndans la variable {{content}}, soit au modèle de page par défaut, soit à\ncelui qui est spécifié dans l’entête YAML Front Matter.\nLe résultat de cette dernière conversion du modèle de page, généralement un\nfichier HTML, est écrit dans votre répertoire de destination.\n\nJ'aimerais maintenant vous montrer un exemple où Jekyll applique cette\ntransformation. Ensuite, lors d’un\ntest complet de génération de site, nous\nirons étudier la structure générale de l’algorithme au\ncœur de Jekyll pour voir quels traitements sont effectués\nsur les différents types de fichiers.\nLa transformation de Jekyll\nLe mécanisme de transformation de Jekyll est situé dans\nla méthode run du fichier renderer,\nqui fait essentiellement la chose suivante, en sautant potentiellement quelques\nétapes :\nafter_liquid = render_with_liquid(file_content) # line 62\nafter_markdown = convert(after_liquid) # line 66\nplace_in_layout(after_markdown) # line 71\nDonc si nous transformons l’article présent dans le thème par défaut de Jekyll :\n---\nlayout: post\ntitle: \"Bienvenue dans Jekyll !\"\ndate: 2016-08-17 13:50:36 +0100\ncategories: jekyll update\n---\n\nYou’ll find this post in your `_posts` directory. Go ahead and edit it and\nre-build the site to see your changes. You can rebuild the site in many\ndifferent ways, but the most common way is to run `jekyll serve`, which launches\na web server and auto-regenerates your site when a file is updated.\n\nTo add new posts, simply add a file in the `_posts` directory that follows the\nconvention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front\nmatter. Take a look at the source for this post to get an idea about how it\nworks.\n\nJekyll also offers powerful support for code snippets:\n\n    def print_hi(name)\n      puts \"Hi, #{name}\"\n    end\n    print_hi('Tom')\n    #=&gt; prints 'Hi, Tom' to STDOUT.\n\nCheck out the [Jekyll docs][jekyll-docs] for more info on how to get the most\nout of Jekyll. File all bugs\/feature requests at [Jekyll’s GitHub\nrepo][jekyll-gh]. If you have questions, you can ask them on [Jekyll\nTalk][jekyll-talk].\n\n[jekyll-docs]: https:\/\/jekyllrb.com\/docs\/home\n[jekyll-gh]: https:\/\/github.com\/jekyll\/jekyll\n[jekyll-talk]: https:\/\/talk.jekyllrb.com\/\n…vous pouvez voir le résultat des deux premières étapes de la transformation :\n\n  En entrée\n  1. Liquidifié\n  2. Converti\n  \n    \nYou’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.\n\nTo add new posts, simply add a file in the `_posts` directory that follows the\nconvention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front\nmatter. Take a look at the source for this post to get an idea about how it\nworks.\n\nJekyll also offers powerful support for code snippets:\n\n    def print_hi(name)\n      puts \"Hi, #{name}\"\n    end\n    print_hi('Tom')\n    #=&gt; prints 'Hi, Tom' to STDOUT.\n\nCheck out the [Jekyll docs][jekyll-docs] for more info on how to get the most\nout of Jekyll. File all bugs\/feature requests at [Jekyll’s GitHub\nrepo][jekyll-gh]. If you have questions, you can ask them on [Jekyll\nTalk][jekyll-talk].\n\n[jekyll-docs]: https:\/\/jekyllrb.com\/docs\/home\n[jekyll-gh]: https:\/\/github.com\/jekyll\/jekyll\n[jekyll-talk]: https:\/\/talk.jekyllrb.com\/\n\n\n\n  \n\n  \n    \nYou’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.\n\nTo add new posts, simply add a file in the `_posts` directory that follows the\nconvention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front\nmatter. Take a look at the source for this post to get an idea about how it\nworks.\n\nJekyll also offers powerful support for code snippets:\n\ndef print_hi(name)\n  puts \"Hi, \\#{name}\"\nend\nprint_hi('Tom')\n\n #=&gt; prints 'Hi, Tom' to STDOUT.\n\nCheck out the [Jekyll docs][jekyll-docs] for more info on how to get the most\nout of Jekyll. File all bugs\/feature requests at [Jekyll’s GitHub\nrepo][jekyll-gh]. If you have questions, you can ask them on [Jekyll\nTalk][jekyll-talk].\n\n[jekyll-docs]: https:\/\/jekyllrb.com\/docs\/home\n[jekyll-gh]: https:\/\/github.com\/jekyll\/jekyll\n[jekyll-talk]: https:\/\/talk.jekyllrb.com\/\n\n\n\n  \n  \n  \nYou’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.\nTo 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.\nJekyll also offers powerful support for code snippets:\ndef print_hi(name)\n  puts \"Hi, \\#{name}\"\nend\nprint_hi('Tom')\n#=&gt; prints 'Hi, Tom' to STDOUT.\nCheck out the Jekyll docs for more info on how to get the most out of Jekyll. File all bugs\/feature requests at Jekyll’s GitHub repo. If you have questions, you can ask them on Jekyll Talk.\n\n\n\nPuis vient la dernière étape où nous mettons tout cela dans la variable\n{{content}} de notre modèle :\n\n  Modèle\n  3. Résultat final\n  \n\n\n  \n    {{ page.title | escape }}\n    {{ page.date | date: \"%b %-d, %Y\" }}{% if page.author %} • {{ page.author }}{% endif %}\n  \n\n  \n    {{ content }}\n  \n\n\n    \n\n\n\n\n  \n    Welcome to Jekyll!\n    Aug 17, 2016\n  \n\n  \n    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.\n\n    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.\n\n    Jekyll also offers powerful support for code snippets:\n\n    \n    def print_hi(name)\n    puts \"Hi, \\#{name}\"\n    end\n    print_hi('Tom')\n    #=&gt; prints 'Hi, Tom' to STDOUT.\n    \n\n    Check out the Jekyll docs for more info on how to get the most out of Jekyll. File all bugs\/feature requests at Jekyll’s GitHub repo. If you have questions, you can ask them on Jekyll Talk.\n\n  \n\n\n    \n  \n\nCeci est juste un exemple de conversion avec kramdown pour le markdown et\nd’insertion du résultat dans un modèle. Jekyll intègre d’autres convertisseurs\ncomme\nsmartypants.\nJekyll inclut aussi par défaut un\nconvertisseur pour Sass dans\nle\nfichier de spécification de sa gem Ruby,\nqui n'est pas un convertisseur intégré, mais vous comprendrez quand vous\nlancerez la commande jekyll new. Vous pouvez installer d’autres convertisseurs\nà l’aide de plugins. Les modèles sont des fichiers qui sont soit stockés dans\nvotre dossier _layouts, soit dans celui empaqueté dans la gem du thème utilisé\npar votre fichier de configuration.\nLe cœur de Jekyll\nMaintenant que vous comprenez l’étape de transformation de Jekyll, regardons\ncomme elle s'intègre dans un processus de génération de site plus global à\npartir de fichiers en entrée.\nSi nous pitons le code exécuté lors de l’invocation de la commande\njekyll build, nous nous apercevons que\nsite.process\nreprésente le cœur de Jekyll. Vous trouverez les parties importantes un peu plus\nbas accompagnées de mes commentaires explicatifs. Reportez-vous à\nla partie sur le debug si vous souhaitez vous baladez à votre tour\ndans l’appel de la méthode.\n# Public: Lit, processe et écrit le Site dans la destination.\n#\n# Ne retourne rien.\ndef process\n  reset # vide tous les layouts\/pages\/static_file\/data\/collections du site.\n  read # lit les fichiers de votre répértoire et les stocke dans un objet Site, les classer dépend\n  # d’où ils se trouvent, ce qu'ils contiennent et de votre fichier de configuration.\n  generate # Vous donne la chance de lancer des générateurs pour ajouter plus de choses au site..\n  render # c'est la méthode chargée de la conversion.\n  cleanup # 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.\n  write # écrit les fichiers dans votre dossier de destination.\n  print_stats\nend\nrender applique la transformation de Jekyll aux fichiers qui possèdent une\nentête YAML Front Matter. Les\ndocuments sont créés à partir des fichiers de collection qui possèdent des\nentêtes YAML Front Matter et les pages sont d’autres documents avec des entêtes\nYAML. Vous devez déclarer les collections dans votre fichier _config.yml,\ncomme ça vous saurez que vous en avez. Vous devez également savoir que\nles posts et les brouillons de posts sont simplement des collections spéciales,\nce sont donc aussi des documents.\ndef render\n    …\n    render_docs(payload) # 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)\n    render_pages(payload) # cette fonction s'occupe du rendu des fichiers qui n'appartiennent pas à des collections mais qui ont des entêtes Front Matter\n    # cela peut être vos fichiers `feed.xml`, `index.html`, `main.scss`, etc.\n    …\nend\n…\n\ndef render_docs(payload)\n  collections.each do |_, collection|\n    collection.docs.each do |document|\n      if regenerator.regenerate?(document)\n        document.output = Jekyll::Renderer.new(self, document, payload).run\n        document.trigger_hooks(:post_render)\n      end\n    end\n  end\nend\n…\ndef render_pages(payload)\n  pages.flatten.each do |page|\n    if regenerator.regenerate?(page)\n      page.output = Jekyll::Renderer.new(self, page, payload).run\n      page.trigger_hooks(:post_render)\n    end\n  end\nend\nTest exhaustif d’une génération\nRegardons à présent ce que nous obtenons lors d’un exemple de génération à\npartir de quelques types de fichiers dans différents répertoires, avec et sans\nl’option --drafts (active ou non la génération des brouillons). Le conteneur\nDocker de ce test est dispos sur\nbytesandwich\/jekyll-outcomes.\nIl recopie ces fichiers :\n\n2016-05-05-post-normal.md # un post normal avec une date passée\n2016-05-05-post-without-frontmatter.md # un post sans frontmatter, avec\nune date passée\n2020-02-02-post-future.md # un post standard, daté dans le futur\nfrontmatter-not-post.md # un fichier avec du frontmatter qui n'est pas un\npost\ntext.txt # un fichier texte normal\nyaml.yml # un fichier YAML normal\n\n… dans chacun de ces répertoires :\n\n\/\n\/_posts\n\/_drafts\n\/_data\n\/_my_output_collection\n\/_my_non_output_collection\n\/_underscore_dir\n\/regular_dir\n\nSauf que pour chaque association de fichier et de répertoire, le nom du\nrépertoire de destination est ajouté à la fin du fichier de manière à ce que\nnous puissions mieux appréhender les corresponsances entre les fichiers d’entrée\net les fichiers de sortie. Vous retrouvez un aperçu du résultat de la commande\ntree après les deux tableaux.\nJ'ai fait un tableau avec une ligne par répertoire et une colonne par fichier,\nla cellule contient l’opération effectuée sur le fichier, qui peut être :\n\ncopié sans altération\nomis\ntransformé et placé dans le répertoire correspondant\npost transformé, qui est ensuite placé dans une arborescence de dossiers,\ncrées d’après la date du post.\n\nGénération des fichiers sans l’option brouillons\n\n  \n    \n       \n      text.txt\n      frontmatter-not-post.md\n      2016-05-05-post-without-frontmatter.md\n      yaml.yml\n      2020-02-02-post-future.md\n      2016-05-05-post-normal.md\n    \n  \n  \n    \n      \/\n      copié\n      transformé\n      copié\n      copié\n      transformé\n      transformé\n    \n    \n      \/posts\n      omis\n      omis\n      post transformé\n      omis\n      omis\n      post transformé\n    \n    \n      \/drafts\n      omis\n      omis\n      omis\n      omis\n      omis\n      omis\n    \n    \n      \/data\n      omis\n      omis\n      omis\n      omis\n      omis\n      omis\n    \n    \n      \/my_output_collection\n      copié\n      transformé\n      copié\n      copié\n      omis\n      transformé\n    \n    \n      \/my_non_output_collection\n      copié\n      omis\n      copié\n      copié\n      omis\n      omis\n    \n    \n      \/underscore_dir\n      omis\n      omis\n      omis\n      omis\n      omis\n      omis\n    \n    \n      \/regular_dir\n      copié\n      transformé\n      copié\n      copié\n      transformé\n      transformé\n    \n  \n\nGénération des fichiers avec l’option brouillon\n\n  \n    \n       \n      text.txt\n      frontmatter-not-post.md\n      2016-05-05-post-without-frontmatter.md\n      yaml.yml\n      2020-02-02-post-future.md\n      2016-05-05-post-normal.md\n    \n  \n  \n    \n      \/\n      copié\n      transformé\n      copié\n      copié\n      transformé\n      transformé\n    \n    \n      \/posts\n      omis\n      omis\n      post transformé\n      omis\n      omis\n      post transformé\n    \n    \n      \/drafts\n      Aucune\n      post transformé\n      post transformé\n      post transformé\n      omis\n      post transformé\n    \n    \n      \/data\n      omis\n      omis\n      omis\n      omis\n      omis\n      omis\n    \n    \n      \/my_output_collection\n      copié\n      transformé\n      copié\n      copié\n      omis\n      transformé\n    \n    \n      \/my_non_output_collection\n      copié\n      omis\n      copié\n      copié\n      omis\n      omis\n    \n    \n      \/underscore_dir\n      omis\n      omis\n      omis\n      omis\n      omis\n      omis\n    \n    \n      \/regular_dir\n      copié\n      transformé\n      copié\n      copié\n      transformé\n      transformé\n    \n  \n\n\n  Source\n  Site\n  Site avec les brouillons\n  \nbash-4.3# tree .\n.\n├── 2016-05-05-post-normal.md\n├── 2016-05-05-post-without-frontmatter.md\n├── 2020-02-02-post-future.md\n├── Gemfile\n├── Gemfile.lock\n├── _config.yml\n├── _data\n│   ├── 2016-05-05-post-normal-data.md\n│   ├── 2016-05-05-post-without-frontmatter-data.md\n│   ├── 2020-02-02-post-future-data.md\n│   ├── frontmatter-not-post-data.md\n│   ├── text-data.txt\n│   └── yaml-data.yml\n├── _drafts\n│   ├── 2016-05-05-post-normal-drafts.md\n│   ├── 2016-05-05-post-without-frontmatter-drafts.md\n│   ├── 2020-02-02-post-future-drafts.md\n│   ├── frontmatter-not-post-drafts.md\n│   ├── text-drafts.txt\n│   └── yaml-drafts.yml\n├── _layouts\n│   └── special.html\n├── _my_non_output_collection\n│   ├── 2016-05-05-post-normal-my_non_output_collection.md\n│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md\n│   ├── 2020-02-02-post-future-my_non_output_collection.md\n│   ├── frontmatter-not-post-my_non_output_collection.md\n│   ├── text-my_non_output_collection.txt\n│   └── yaml-my_non_output_collection.yml\n├── _my_output_collection\n│   ├── 2016-05-05-post-normal-my_output_collection.md\n│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md\n│   ├── 2020-02-02-post-future-my_output_collection.md\n│   ├── frontmatter-not-post-my_output_collection.md\n│   ├── text-my_output_collection.txt\n│   └── yaml-my_output_collection.yml\n├── _posts\n│   ├── 2016-05-05-post-normal-posts.md\n│   ├── 2016-05-05-post-without-frontmatter-posts.md\n│   ├── 2016-09-14-welcome-to-jekyll.markdown\n│   ├── 2020-02-02-post-future-posts.md\n│   ├── frontmatter-not-post-posts.md\n│   ├── text-posts.txt\n│   └── yaml-posts.yml\n├── _underscore_dir\n│   ├── 2016-05-05-post-normal-underscore_dir.md\n│   ├── 2016-05-05-post-without-frontmatter-underscore_dir.md\n│   ├── 2020-02-02-post-future-underscore_dir.md\n│   ├── frontmatter-not-post-underscore_dir.md\n│   ├── text-underscore_dir.txt\n│   └── yaml-underscore_dir.yml\n├── about.md\n├── css\n│   └── main.scss\n├── feed.xml\n├── frontmatter-not-post.md\n├── index.html\n├── regular_dir\n│   ├── 2016-05-05-post-normal-regular_dir.md\n│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md\n│   ├── 2020-02-02-post-future-regular_dir.md\n│   ├── frontmatter-not-post-regular_dir.md\n│   ├── text-regular_dir.txt\n│   └── yaml-regular_dir.yml\n├── text.txt\n└── yaml.yml\n\n9 directories, 57 files \n\n  \n  \nbash-4.3# tree _site\n_site\n├── 2016\n│   └── 05\n│       └── 05\n│           ├── post-normal-posts.html\n│           └── post-without-frontmatter-posts.html\n├── 2016-05-05-post-normal.html\n├── 2016-05-05-post-without-frontmatter.md\n├── 2020-02-02-post-future.html\n├── Gemfile\n├── Gemfile.lock\n├── about\n│   └── index.html\n├── css\n│   └── main.css\n├── feed.xml\n├── frontmatter-not-post.html\n├── index.html\n├── jekyll\n│   └── update\n│       └── 2016\n│           └── 09\n│               └── 14\n│                   └── welcome-to-jekyll.html\n├── my_non_output_collection\n│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md\n│   ├── text-my_non_output_collection.txt\n│   └── yaml-my_non_output_collection.yml\n├── my_output_collection\n│   ├── 2016-05-05-post-normal-my_output_collection.html\n│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md\n│   ├── frontmatter-not-post-my_output_collection.html\n│   ├── text-my_output_collection.txt\n│   └── yaml-my_output_collection.yml\n├── regular_dir\n│   ├── 2016-05-05-post-normal-regular_dir.html\n│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md\n│   ├── 2020-02-02-post-future-regular_dir.html\n│   ├── frontmatter-not-post-regular_dir.html\n│   ├── text-regular_dir.txt\n│   └── yaml-regular_dir.yml\n├── text.txt\n└── yaml.yml\n\n13 directories, 29 files\n\n\nbash-4.3# tree _site\n_site\n├── 2016\n│   ├── 05\n│   │   └── 05\n│   │       ├── post-normal-drafts.html\n│   │       ├── post-normal-posts.html\n│   │       ├── post-without-frontmatter-drafts.html\n│   │       └── post-without-frontmatter-posts.html\n│   └── 09\n│       └── 14\n│           ├── frontmatter-not-post-drafts.html\n│           ├── text-drafts.txt\n│           └── yaml-drafts.yml\n├── 2016-05-05-post-normal.html\n├── 2016-05-05-post-without-frontmatter.md\n├── 2020-02-02-post-future.html\n├── Gemfile\n├── Gemfile.lock\n├── about\n│   └── index.html\n├── css\n│   └── main.css\n├── feed.xml\n├── frontmatter-not-post.html\n├── index.html\n├── jekyll\n│   └── update\n│       └── 2016\n│           └── 09\n│               └── 14\n│                   └── welcome-to-jekyll.html\n├── my_non_output_collection\n│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md\n│   ├── text-my_non_output_collection.txt\n│   └── yaml-my_non_output_collection.yml\n├── my_output_collection\n│   ├── 2016-05-05-post-normal-my_output_collection.html\n│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md\n│   ├── frontmatter-not-post-my_output_collection.html\n│   ├── text-my_output_collection.txt\n│   └── yaml-my_output_collection.yml\n├── regular_dir\n│   ├── 2016-05-05-post-normal-regular_dir.html\n│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md\n│   ├── 2020-02-02-post-future-regular_dir.html\n│   ├── frontmatter-not-post-regular_dir.html\n│   ├── text-regular_dir.txt\n│   └── yaml-regular_dir.yml\n├── text.txt\n└── yaml.yml\n\n15 directories, 34 files ",
      "content_html": "<aside class=\"note note-intro\"><p>Si vous suivez ce blog, vous savez déjà que Jekyll est un\ngénérateur de site statique développé en Ruby. Jack Phelan a décidé d’aller\njeter un œil dans le moteur de Jekyll histoire de mieux comprendre comment sont\ntraités les différents types de fichiers qui sont passés en entrée. Nous\ntraduisons\n<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>\nafin de vous inciter à plonger un peu dans le code de Jekyll et prendre\nconnaissance des concepts fondamentaux de ce générateur. Nous espérons que cela\nvous permettra de mieux appréhender la philosophie de Jekyll ou que cela vous\nsera utile si vous songez à développer un plugin.</p></aside>\n<style type=\"text/css\">\n\n.tabs {\n    position: relative;\n    margin: 25px 0;\n}\n.tab {\n    display: inline-block;\n    padding: 8px 18px;\n    border: 1px solid #4A21CC;\n    margin: 0;\n    margin-left: -1px;\n    position: relative;\n    left: 4px;\n    bottom: -1px;\n    border-top-left-radius: 3px;\n    border-top-right-radius: 3px;\n    cursor: pointer;\n}\n.tab.tab-selected {\n    background-color: #4A21CC;\n    color: white;\n    border-bottom: 1px solid #4A21CC;\n}\n\ntable {\n  font-size: 0.775em;\n  margin: 2em calc(50% - 50vw);\n}\n\ntable tr,\ntable td,\ntable th {\n  border: 1px solid rgba(35,27,48,.2);\n  border-collapse: collapse;\n  padding: .5em;\n}\n\nth, tr td:first-child {\n  font-weight: bold;\n  background-color: rgba(74,33,294,.75);\n  color: white;\n}\n\n.copied {\n  background-color: rgba(125,172,255,.2);\n}\n.transformed {\n  background-color: rgba(204,204,32,.2);\n}\n.post-transformed {\n  background-color: rgba(64,204,32,.2);\n}\n</style>\n<script src=\"https://code.jquery.com/jquery-3.1.1.min.js\" integrity=\"sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=\" crossorigin=\"anonymous\"></script>\n<script type=\"text/javascript\">\n    function activateTab(tab) {\n        var $tabs = $(tab).closest('.tabs')\n        var index = $(tab).index()\n        var $tabAreas = $tabs.find('.tab-content')\n        var $toShow = $tabAreas.eq(index);\n\n        $tabs.find('.tab-selected').removeClass('tab-selected');\n        $(tab).addClass('tab-selected');\n\n        $tabAreas.hide()\n        $toShow.show()\n    }\n    $(() => {\n      $(\".tab\").click((e) => activateTab(e.target))\n      $('.tabs').each((i, tabs) => activateTab($(tabs).find('.tab').first().get()));\n    })\n</script>\n<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\nJekyll ne fait pas grand-chose à vos fichiers, si ce n'est qu'il les classifie\nde différentes façons.</p>\n<p>Jekyll va soit copier, soit omettre, soit transformer les fichiers du répertoire\nsource dans le répertoire de destination[^1]. Lorsque Jekyll transforme vos\nfichiers, c'est toujours de cette manière, si ce n'est que la deuxième étape\npeut être potentiellement sautée.[^2]</p>\n<p>Si un fichier commence par une entête\n<a href=\"https://jekyllrb.com/docs/frontmatter/\" target=\"_blank\" rel=\"noopener noreferrer\">YAML Front Matter</a> Jekyll va appliquer\nles transformations suivante au fichier:</p>\n<ol>\n<li><strong>Interprétation du code Liquid</strong> : Le contenu du fichier est d’abord\nparcouru par le parser de <a href=\"https://shopify.github.io/liquid/\" target=\"_blank\" rel=\"noopener noreferrer\">Liquid</a>, les\nvariables comme <code>site</code> ou <code>page</code> auxquelles le modèle Liquid veut accéder\nsont alors interprétées.</li>\n<li><strong>Conversion du contenu</strong> : en fonction de l’extension de fichier, Jekyll\nfait appel à un convertisseur dédié, par example Kramdown pour les fichiers\n<code>.md</code> ou <code>.markdown</code>, qui est chargé de convertir le résultat obtenu après\nl’étape 1.</li>\n<li><strong>Parsing du modèle</strong>: Le résultat de cette conversion est alors transmis\ndans la variable <code>{{content}}</code>, soit au modèle de page par défaut, soit à\ncelui qui est spécifié dans l’entête YAML Front Matter.</li>\n<li>Le résultat de cette dernière conversion du modèle de page, généralement un\nfichier HTML, est écrit dans votre répertoire de destination.</li>\n</ol>\n<p>J'aimerais maintenant vous montrer un exemple où Jekyll applique cette\ntransformation. Ensuite, lors d’un\n<a href=\"#test-exhaustif-dune-génération\">test complet</a> de génération de site, nous\nirons étudier la structure générale de l’algorithme au\n<a href=\"#le-cœur-de-jekyll\">cœur de Jekyll</a> pour voir quels traitements sont effectués\nsur les différents types de fichiers.</p>\n<h2 id=\"la-transformation-de-jekyll\">La transformation de Jekyll</h2>\n<p>Le mécanisme de transformation de Jekyll est situé dans\n<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>,\nqui fait essentiellement la chose suivante, en sautant potentiellement quelques\nétapes :</p>\n<pre><code class=\"language-ruby hljs ruby\">after_liquid = render_with_liquid(file_content) <span class=\"hljs-comment\"># line 62</span>\nafter_markdown = convert(after_liquid) <span class=\"hljs-comment\"># line 66</span>\nplace_in_layout(after_markdown) <span class=\"hljs-comment\"># line 71</span></code></pre>\n<p>Donc si nous transformons l’article présent dans le thème par défaut de Jekyll :</p>\n<pre><code class=\"language-markdown hljs markdown\">---\nlayout: post\ntitle: \"Bienvenue dans Jekyll !\"\ndate: 2016-08-17 13:50:36 +0100\n<span class=\"hljs-section\">categories: jekyll update\n---</span>\n\nYou’ll find this post in your <span class=\"hljs-code\">`_posts`</span> directory. Go ahead and edit it and\nre-build the site to see your changes. You can rebuild the site in many\ndifferent ways, but the most common way is to run <span class=\"hljs-code\">`jekyll serve`</span>, which launches\na web server and auto-regenerates your site when a file is updated.\n\nTo add new posts, simply add a file in the <span class=\"hljs-code\">`_posts`</span> directory that follows the\nconvention <span class=\"hljs-code\">`YYYY-MM-DD-name-of-post.ext`</span> and includes the necessary front\nmatter. Take a look at the source for this post to get an idea about how it\nworks.\n\nJekyll also offers powerful support for code snippets:\n\n<span class=\"hljs-code\">    def print_hi(name)</span>\n<span class=\"hljs-code\">      puts \"Hi, #{name}\"</span>\n<span class=\"hljs-code\">    end</span>\n<span class=\"hljs-code\">    print_hi('Tom')</span>\n<span class=\"hljs-code\">    #=&gt; prints 'Hi, Tom' to STDOUT.</span>\n\nCheck 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\nout of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub\nrepo][jekyll-gh]. If you have questions, you can ask them on [Jekyll\nTalk][jekyll-talk].\n\n[<span class=\"hljs-symbol\">jekyll-docs</span>]: <span class=\"hljs-link\">https://jekyllrb.com/docs/home</span>\n[<span class=\"hljs-symbol\">jekyll-gh</span>]: <span class=\"hljs-link\">https://github.com/jekyll/jekyll</span>\n[<span class=\"hljs-symbol\">jekyll-talk</span>]: <span class=\"hljs-link\">https://talk.jekyllrb.com/</span></code></pre>\n<p>…vous pouvez voir le résultat des deux premières étapes de la transformation :</p>\n<div class=\"tabs\">\n  <div class=\"tab\">En entrée</div>\n  <div class=\"tab\">1. Liquidifié</div>\n  <div class=\"tab\">2. Converti</div>\n  <div class=\"tab-content\">\n    <pre><code class=\"language-markdown\">\nYou’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.\n\nTo add new posts, simply add a file in the `_posts` directory that follows the\nconvention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front\nmatter. Take a look at the source for this post to get an idea about how it\nworks.\n\nJekyll also offers powerful support for code snippets:\n\n    def print_hi(name)\n      puts \"Hi, #{name}\"\n    end\n    print_hi('Tom')\n    #=&gt; prints 'Hi, Tom' to STDOUT.\n\nCheck out the [Jekyll docs][jekyll-docs] for more info on how to get the most\nout of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub\nrepo][jekyll-gh]. If you have questions, you can ask them on [Jekyll\nTalk][jekyll-talk].\n\n[jekyll-docs]: https://jekyllrb.com/docs/home\n[jekyll-gh]: https://github.com/jekyll/jekyll\n[jekyll-talk]: https://talk.jekyllrb.com/\n\n</code></pre>\n\n  </div>\n\n  <div class=\"tab-content\">\n    <pre><code class=\"language-markdown\">\nYou’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.\n\nTo add new posts, simply add a file in the `_posts` directory that follows the\nconvention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front\nmatter. Take a look at the source for this post to get an idea about how it\nworks.\n\nJekyll also offers powerful support for code snippets:\n\n<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>\n  <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>\n<span class=\"k\">end</span>\n<span class=\"n\">print_hi</span><span class=\"p\">(</span><span class=\"s1\">'Tom'</span><span class=\"p\">)</span>\n<span class=\"c1\">\n #=&gt; prints 'Hi, Tom' to STDOUT.</span></code></pre></figure>\n\nCheck out the [Jekyll docs][jekyll-docs] for more info on how to get the most\nout of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub\nrepo][jekyll-gh]. If you have questions, you can ask them on [Jekyll\nTalk][jekyll-talk].\n\n[jekyll-docs]: https://jekyllrb.com/docs/home\n[jekyll-gh]: https://github.com/jekyll/jekyll\n[jekyll-talk]: https://talk.jekyllrb.com/\n\n</code></pre>\n\n  </div>\n  <div class=\"tab-content\">\n  <pre><code class=\"language-markdown\">\n<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>\n<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>\n<p>Jekyll also offers powerful support for code snippets:</p>\n<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>\n  <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>\n<span class=\"k\">end</span>\n<span class=\"n\">print_hi</span><span class=\"p\">(</span><span class=\"s1\">'Tom'</span><span class=\"p\">)</span>\n<span class=\"c1\">#=&gt; prints 'Hi, Tom' to STDOUT.</span></code></pre></figure>\n<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>\n</code></pre>\n</div>\n</div>\n<p>Puis vient la dernière étape où nous mettons tout cela dans la variable\n<code>{{content}}</code> de notre modèle :</p>\n<div class=\"tabs\">\n  <div class=\"tab\">Modèle</div>\n  <div class=\"tab\">3. Résultat final</div>\n  <div class=\"tab-content\"><pre><code class=\"language-markdown\">\n<article class=\"post\" itemscope itemtype=\"https://schema.org/BlogPosting\">\n\n  <header class=\"post-header\">\n    <h1 class=\"post-title\" itemprop=\"name headline\">{{ page.title | escape }}</h1>\n    <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>\n  </header>\n\n  <div class=\"post-content\" itemprop=\"articleBody\">\n    {{ content }}\n  </div>\n\n</article>\n    </code></pre></div>\n<div class=\"tab-content\">\n<pre><code class=\"language-markdown\">\n<article class=\"post\" itemscope itemtype=\"https://schema.org/BlogPosting\">\n\n  <header class=\"post-header\">\n    <h1 class=\"post-title\" itemprop=\"name headline\">Welcome to Jekyll!</h1>\n    <p class=\"post-meta\"><time datetime=\"2016-08-17T23:50:36-04:00\" itemprop=\"datePublished\">Aug 17, 2016</time></p>\n  </header>\n\n  <div class=\"post-content\" itemprop=\"articleBody\">\n    <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>\n\n    <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>\n\n    <p>Jekyll also offers powerful support for code snippets:</p>\n\n    <figure class=\"highlight\"><pre><code class=\"language-ruby\" data-lang=\"ruby\">\n    <span class=\"k\">def</span> <span class=\"nf\">print_hi</span><span class=\"p\">(</span><span class=\"nb\">name</span><span class=\"p\">)</span>\n    <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>\n    <span class=\"k\">end</span>\n    <span class=\"n\">print_hi</span><span class=\"p\">(</span><span class=\"s1\">'Tom'</span><span class=\"p\">)</span>\n    <span class=\"c1\">#=&gt; prints 'Hi, Tom' to STDOUT.</span></code></pre>\n    </figure>\n\n    <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>\n\n  </div>\n\n</article>\n    </code></pre>\n  </div>\n</div>\n<p>Ceci est juste un exemple de conversion avec kramdown pour le markdown et\nd’insertion du résultat dans un modèle. Jekyll intègre d’autres convertisseurs\ncomme\n<a href=\"https://github.com/jekyll/jekyll/blob/2b15b0b3251d35c290dc96eb07e18fa31a58bcc6/lib/jekyll/converters/smartypants.rb\" target=\"_blank\" rel=\"noopener noreferrer\">smartypants</a>.\nJekyll inclut aussi par défaut un\n<a href=\"https://github.com/jekyll/jekyll-sass-converter\" target=\"_blank\" rel=\"noopener noreferrer\">convertisseur pour Sass</a> dans\nle\n<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>,\nqui n'est pas un convertisseur intégré, mais vous comprendrez quand vous\nlancerez la commande <code>jekyll new</code>. Vous pouvez installer d’autres convertisseurs\nà l’aide de plugins. Les modèles sont des fichiers qui sont soit stockés dans\nvotre dossier <code>_layouts</code>, soit dans celui empaqueté dans la gem du thème utilisé\npar votre fichier de configuration.</p>\n<h2 id=\"le-c艙ur-de-jekyll\">Le cœur de Jekyll</h2>\n<p>Maintenant que vous comprenez l’étape de transformation de Jekyll, regardons\ncomme elle s'intègre dans un processus de génération de site plus global à\npartir de fichiers en entrée.</p>\n<p>Si nous pitons le code exécuté lors de l’invocation de la commande\n<code>jekyll build</code>, nous nous apercevons que\n<a href=\"https://github.com/jekyll/jekyll/blob/2b15b0b3251d35c290dc96eb07e18fa31a58bcc6/lib/jekyll/site.rb#L65\" target=\"_blank\" rel=\"noopener noreferrer\"><code>site.process</code></a>\nreprésente le cœur de Jekyll. Vous trouverez les parties importantes un peu plus\nbas accompagnées de mes commentaires explicatifs. Reportez-vous à\n<a href=\"#debug\">la partie sur le debug</a> si vous souhaitez vous baladez à votre tour\ndans l’appel de la méthode.</p>\n<pre><code class=\"language-ruby hljs ruby\"><span class=\"hljs-comment\"># Public: Lit, processe et écrit le Site dans la destination.</span>\n<span class=\"hljs-comment\">#</span>\n<span class=\"hljs-comment\"># Ne retourne rien.</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">def</span> <span class=\"hljs-title\">process</span></span>\n  reset <span class=\"hljs-comment\"># vide tous les layouts/pages/static_file/data/collections du site.</span>\n  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>\n  <span class=\"hljs-comment\"># d’où ils se trouvent, ce qu'ils contiennent et de votre fichier de configuration.</span>\n  generate <span class=\"hljs-comment\"># Vous donne la chance de lancer des générateurs pour ajouter plus de choses au site..</span>\n  render <span class=\"hljs-comment\"># c'est la méthode chargée de la conversion.</span>\n  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>\n  write <span class=\"hljs-comment\"># écrit les fichiers dans votre dossier de destination.</span>\n  print_stats\n<span class=\"hljs-keyword\">end</span></code></pre>\n<p><code>render</code> applique la transformation de Jekyll aux fichiers qui possèdent une\nentête <a href=\"https://jekyllrb.com/docs/frontmatter/\" target=\"_blank\" rel=\"noopener noreferrer\">YAML Front Matter</a>. Les\ndocuments sont créés à partir des fichiers de collection qui possèdent des\nentêtes YAML Front Matter et les pages sont d’autres documents avec des entêtes\nYAML. Vous devez déclarer les collections dans votre fichier <code>_config.yml</code>,\ncomme ça vous saurez que vous en avez. Vous devez également savoir que\n<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>,\nce sont donc aussi des documents.</p>\n<pre><code class=\"language-ruby hljs ruby\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">def</span> <span class=\"hljs-title\">render</span></span>\n    …\n    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>\n    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>\n    <span class=\"hljs-comment\"># cela peut être vos fichiers `feed.xml`, `index.html`, `main.scss`, etc.</span>\n    …\n<span class=\"hljs-keyword\">end</span>\n…\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">def</span> <span class=\"hljs-title\">render_docs</span><span class=\"hljs-params\">(payload)</span></span>\n  collections.each <span class=\"hljs-keyword\">do</span> <span class=\"hljs-params\">|_, collection|</span>\n    collection.docs.each <span class=\"hljs-keyword\">do</span> <span class=\"hljs-params\">|document|</span>\n      <span class=\"hljs-keyword\">if</span> regenerator.regenerate?(document)\n        document.output = Jekyll::Renderer.new(<span class=\"hljs-keyword\">self</span>, document, payload).run\n        document.trigger_hooks(<span class=\"hljs-symbol\">:post_render</span>)\n      <span class=\"hljs-keyword\">end</span>\n    <span class=\"hljs-keyword\">end</span>\n  <span class=\"hljs-keyword\">end</span>\n<span class=\"hljs-keyword\">end</span>\n…\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">def</span> <span class=\"hljs-title\">render_pages</span><span class=\"hljs-params\">(payload)</span></span>\n  pages.flatten.each <span class=\"hljs-keyword\">do</span> <span class=\"hljs-params\">|page|</span>\n    <span class=\"hljs-keyword\">if</span> regenerator.regenerate?(page)\n      page.output = Jekyll::Renderer.new(<span class=\"hljs-keyword\">self</span>, page, payload).run\n      page.trigger_hooks(<span class=\"hljs-symbol\">:post_render</span>)\n    <span class=\"hljs-keyword\">end</span>\n  <span class=\"hljs-keyword\">end</span>\n<span class=\"hljs-keyword\">end</span></code></pre>\n<h2 id=\"test-exhaustif-d-une-generation\">Test exhaustif d’une génération</h2>\n<p>Regardons à présent ce que nous obtenons lors d’un exemple de génération à\npartir de quelques types de fichiers dans différents répertoires, avec et sans\nl’option <code>--drafts</code> (active ou non la génération des brouillons). Le conteneur\nDocker de ce test est dispos sur\n<a href=\"https://github.com/bytesandwich/jekyll-outcomes\" target=\"_blank\" rel=\"noopener noreferrer\">bytesandwich/jekyll-outcomes</a>.</p>\n<p>Il recopie ces fichiers :</p>\n<ul>\n<li><strong>2016-05-05-post-normal.md</strong> <em># un post normal avec une date passée</em></li>\n<li><strong>2016-05-05-post-without-frontmatter.md</strong> <em># un post sans frontmatter, avec\nune date passée</em></li>\n<li><strong>2020-02-02-post-future.md</strong> <em># un post standard, daté dans le futur</em></li>\n<li><strong>frontmatter-not-post.md</strong> <em># un fichier avec du frontmatter qui n'est pas un\npost</em></li>\n<li><strong>text.txt</strong> <em># un fichier texte normal</em></li>\n<li><strong>yaml.yml</strong> <em># un fichier YAML normal</em></li>\n</ul>\n<p>… dans chacun de ces répertoires :</p>\n<ul>\n<li><strong>/</strong></li>\n<li><strong>/_posts</strong></li>\n<li><strong>/_drafts</strong></li>\n<li><strong>/_data</strong></li>\n<li><strong>/_my_output_collection</strong></li>\n<li><strong>/_my_non_output_collection</strong></li>\n<li><strong>/_underscore_dir</strong></li>\n<li><strong>/regular_dir</strong></li>\n</ul>\n<p>Sauf que pour chaque association de fichier et de répertoire, le nom du\nrépertoire de destination est ajouté à la fin du fichier de manière à ce que\nnous puissions mieux appréhender les corresponsances entre les fichiers d’entrée\net les fichiers de sortie. Vous retrouvez un aperçu du résultat de la commande\n<code>tree</code> après les deux tableaux.</p>\n<p>J'ai fait un tableau avec une ligne par répertoire et une colonne par fichier,\nla cellule contient l’opération effectuée sur le fichier, qui peut être :</p>\n<ul>\n<li><em>copié</em> sans altération</li>\n<li><em>omis</em></li>\n<li><em>transformé</em> et placé dans le répertoire correspondant</li>\n<li><em>post transformé</em>, qui est ensuite placé dans une arborescence de dossiers,\ncrées d’après la date du post.</li>\n</ul>\n<h2 id=\"generation-des-fichiers-sans-l-option-brouillons\">Génération des fichiers sans l’option brouillons</h2>\n<table>\n  <thead>\n    <tr>\n      <th> </th>\n      <th>text.txt</th>\n      <th>frontmatter-not-post.md</th>\n      <th>2016-05-05-post-without-frontmatter.md</th>\n      <th>yaml.yml</th>\n      <th>2020-02-02-post-future.md</th>\n      <th>2016-05-05-post-normal.md</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>/</th>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"transformed\">transformé</td>\n    </tr>\n    <tr>\n      <td>/posts</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"post-transformed\">post transformé</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"post-transformed\">post transformé</td>\n    </tr>\n    <tr>\n      <td>/drafts</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n    </tr>\n    <tr>\n      <td>/data</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n    </tr>\n    <tr>\n      <td>/my_output_collection</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"transformed\">transformé</td>\n    </tr>\n    <tr>\n      <td>/my_non_output_collection</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n    </tr>\n    <tr>\n      <td>/underscore_dir</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n      <td class=\"omitted\">omis</td>\n    </tr>\n    <tr>\n      <td>/regular_dir</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"transformed\">transformé</td>\n    </tr>\n  </tbody>\n</table>\n<h2 id=\"generation-des-fichiers-avec-l-option-brouillon\">Génération des fichiers avec l’option brouillon</h2>\n<table>\n  <thead>\n    <tr>\n      <th> </th>\n      <th>text.txt</th>\n      <th>frontmatter-not-post.md</th>\n      <th>2016-05-05-post-without-frontmatter.md</th>\n      <th>yaml.yml</th>\n      <th>2020-02-02-post-future.md</th>\n      <th>2016-05-05-post-normal.md</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>/</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"transformed\">transformé</td>\n    </tr>\n    <tr>\n      <td>/posts</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td class=\"post-transformed\">post transformé</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td class=\"post-transformed\">post transformé</td>\n    </tr>\n    <tr>\n      <td>/drafts</td>\n      <td>Aucune</td>\n      <td class=\"post-transformed\">post transformé</td>\n      <td class=\"post-transformed\">post transformé</td>\n      <td class=\"post-transformed\">post transformé</td>\n      <td>omis</td>\n      <td class=\"post-transformed\">post transformé</td>\n    </tr>\n    <tr>\n      <td>/data</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td>omis</td>\n    </tr>\n    <tr>\n      <td>/my_output_collection</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"copied\">copié</td>\n      <td>omis</td>\n      <td class=\"transformed\">transformé</td>\n    </tr>\n    <tr>\n      <td>/my_non_output_collection</td>\n      <td class=\"copied\">copié</td>\n      <td>omis</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"copied\">copié</td>\n      <td>omis</td>\n      <td>omis</td>\n    </tr>\n    <tr>\n      <td>/underscore_dir</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td>omis</td>\n      <td>omis</td>\n    </tr>\n    <tr>\n      <td>/regular_dir</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"copied\">copié</td>\n      <td class=\"transformed\">transformé</td>\n      <td class=\"transformed\">transformé</td>\n    </tr>\n  </tbody>\n</table>\n<div class=\"tabs\">\n  <div class=\"tab tab-selected\">Source</div>\n  <div class=\"tab\">Site</div>\n  <div class=\"tab\">Site avec les brouillons</div>\n  <div class=\"tab-content\"><pre><code class=\"language-shell\">\nbash-4.3# tree .\n.\n├── 2016-05-05-post-normal.md\n├── 2016-05-05-post-without-frontmatter.md\n├── 2020-02-02-post-future.md\n├── Gemfile\n├── Gemfile.lock\n├── _config.yml\n├── _data\n│   ├── 2016-05-05-post-normal-data.md\n│   ├── 2016-05-05-post-without-frontmatter-data.md\n│   ├── 2020-02-02-post-future-data.md\n│   ├── frontmatter-not-post-data.md\n│   ├── text-data.txt\n│   └── yaml-data.yml\n├── _drafts\n│   ├── 2016-05-05-post-normal-drafts.md\n│   ├── 2016-05-05-post-without-frontmatter-drafts.md\n│   ├── 2020-02-02-post-future-drafts.md\n│   ├── frontmatter-not-post-drafts.md\n│   ├── text-drafts.txt\n│   └── yaml-drafts.yml\n├── _layouts\n│   └── special.html\n├── _my_non_output_collection\n│   ├── 2016-05-05-post-normal-my_non_output_collection.md\n│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md\n│   ├── 2020-02-02-post-future-my_non_output_collection.md\n│   ├── frontmatter-not-post-my_non_output_collection.md\n│   ├── text-my_non_output_collection.txt\n│   └── yaml-my_non_output_collection.yml\n├── _my_output_collection\n│   ├── 2016-05-05-post-normal-my_output_collection.md\n│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md\n│   ├── 2020-02-02-post-future-my_output_collection.md\n│   ├── frontmatter-not-post-my_output_collection.md\n│   ├── text-my_output_collection.txt\n│   └── yaml-my_output_collection.yml\n├── _posts\n│   ├── 2016-05-05-post-normal-posts.md\n│   ├── 2016-05-05-post-without-frontmatter-posts.md\n│   ├── 2016-09-14-welcome-to-jekyll.markdown\n│   ├── 2020-02-02-post-future-posts.md\n│   ├── frontmatter-not-post-posts.md\n│   ├── text-posts.txt\n│   └── yaml-posts.yml\n├── _underscore_dir\n│   ├── 2016-05-05-post-normal-underscore_dir.md\n│   ├── 2016-05-05-post-without-frontmatter-underscore_dir.md\n│   ├── 2020-02-02-post-future-underscore_dir.md\n│   ├── frontmatter-not-post-underscore_dir.md\n│   ├── text-underscore_dir.txt\n│   └── yaml-underscore_dir.yml\n├── about.md\n├── css\n│   └── main.scss\n├── feed.xml\n├── frontmatter-not-post.md\n├── index.html\n├── regular_dir\n│   ├── 2016-05-05-post-normal-regular_dir.md\n│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md\n│   ├── 2020-02-02-post-future-regular_dir.md\n│   ├── frontmatter-not-post-regular_dir.md\n│   ├── text-regular_dir.txt\n│   └── yaml-regular_dir.yml\n├── text.txt\n└── yaml.yml\n\n9 directories, 57 files </code></pre>\n\n  </div>\n  <div class=\"tab-content\"><pre><code class=\"language-shell\">\nbash-4.3# tree _site\n_site\n├── 2016\n│   └── 05\n│       └── 05\n│           ├── post-normal-posts.html\n│           └── post-without-frontmatter-posts.html\n├── 2016-05-05-post-normal.html\n├── 2016-05-05-post-without-frontmatter.md\n├── 2020-02-02-post-future.html\n├── Gemfile\n├── Gemfile.lock\n├── about\n│   └── index.html\n├── css\n│   └── main.css\n├── feed.xml\n├── frontmatter-not-post.html\n├── index.html\n├── jekyll\n│   └── update\n│       └── 2016\n│           └── 09\n│               └── 14\n│                   └── welcome-to-jekyll.html\n├── my_non_output_collection\n│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md\n│   ├── text-my_non_output_collection.txt\n│   └── yaml-my_non_output_collection.yml\n├── my_output_collection\n│   ├── 2016-05-05-post-normal-my_output_collection.html\n│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md\n│   ├── frontmatter-not-post-my_output_collection.html\n│   ├── text-my_output_collection.txt\n│   └── yaml-my_output_collection.yml\n├── regular_dir\n│   ├── 2016-05-05-post-normal-regular_dir.html\n│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md\n│   ├── 2020-02-02-post-future-regular_dir.html\n│   ├── frontmatter-not-post-regular_dir.html\n│   ├── text-regular_dir.txt\n│   └── yaml-regular_dir.yml\n├── text.txt\n└── yaml.yml\n\n13 directories, 29 files</code></pre></div>\n\n<div class=\"tab-content\"><pre><code class=\"language-shell\">\nbash-4.3# tree _site\n_site\n├── 2016\n│   ├── 05\n│   │   └── 05\n│   │       ├── post-normal-drafts.html\n│   │       ├── post-normal-posts.html\n│   │       ├── post-without-frontmatter-drafts.html\n│   │       └── post-without-frontmatter-posts.html\n│   └── 09\n│       └── 14\n│           ├── frontmatter-not-post-drafts.html\n│           ├── text-drafts.txt\n│           └── yaml-drafts.yml\n├── 2016-05-05-post-normal.html\n├── 2016-05-05-post-without-frontmatter.md\n├── 2020-02-02-post-future.html\n├── Gemfile\n├── Gemfile.lock\n├── about\n│   └── index.html\n├── css\n│   └── main.css\n├── feed.xml\n├── frontmatter-not-post.html\n├── index.html\n├── jekyll\n│   └── update\n│       └── 2016\n│           └── 09\n│               └── 14\n│                   └── welcome-to-jekyll.html\n├── my_non_output_collection\n│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md\n│   ├── text-my_non_output_collection.txt\n│   └── yaml-my_non_output_collection.yml\n├── my_output_collection\n│   ├── 2016-05-05-post-normal-my_output_collection.html\n│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md\n│   ├── frontmatter-not-post-my_output_collection.html\n│   ├── text-my_output_collection.txt\n│   └── yaml-my_output_collection.yml\n├── regular_dir\n│   ├── 2016-05-05-post-normal-regular_dir.html\n│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md\n│   ├── 2020-02-02-post-future-regular_dir.html\n│   ├── frontmatter-not-post-regular_dir.html\n│   ├── text-regular_dir.txt\n│   └── yaml-regular_dir.yml\n├── text.txt\n└── yaml.yml\n\n15 directories, 34 files </code></pre></div></div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/12/09/des-commentaires-statiques-avec-jekyll-et-staticman/",
      "url": "https://jamstatic.fr/2016/12/09/des-commentaires-statiques-avec-jekyll-et-staticman/",
      "title": "Des commentaires statiques avec Jekyll et Staticman",
      "summary": "Utilisation de Staticman pour ajouter des commentaires et des notifications de réponses sur un site statique sous Jekyll",
      "date_published": "2016-12-09T00:00:00+00:00","content_text": "Michael Rose, l’auteur du thème Jekyll Minimal Mistakes, 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 Staticman, un service open-source développé par Eduardo Bouças, 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 Jekyll AWS comment.\nDepuis que j'ai quitté Disqus pour un système de commentaires statiques, Staticman a muri avec des ajouts de fonctionnalités comme les fils de commentaires et les notifications par mail.\nÀ l’aide des instructions fournies par Eduardo Bouças dans cette issue GitHub, je me suis lancé dans l’amélioration de l’expérience relative aux commentaires sur Made Mistakes. Voici comme j'ai procédé.\nPasser à la version 2 de Staticman\nPour tirer parti de ces nouvelles fonctionnalités, il était nécessaire de migrer les paramètres de Staticman du fichier _config.yml de Jekyll vers un nouveau fichier staticman.yml1. Comme il n'y a eu aucun changement dans les paramètres, la transition vers la version 2 était grandement simplifiée.\ncomments:\n  allowedFields     : ['name', 'email', 'url', 'message']\n  branch            : \"master\"\n  commitMessage     : \"Nouveau commentaire.\"\n  filename          : \"commentaire-{@timestamp}\"\n  format            : \"yaml\"\n  moderation        : true\n  path              : \"src\/_data\/comments\/{options.slug}\"\n  requiredFields    : ['name', 'email', 'message']\n  transforms:\n    email           : md5\n  generatedFields:\n    date:\n      type          : \"date\"\n      options:\n        format      : \"iso8601\"\nNouvelles options de configuration\nAssurez-vous de jeter un œil au modèle de fichier de configuration et à la liste complète des paramètres pour vous faire une idée des possibilités de configuration.\nPar 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 staticman.yml.\nSupprimer\/Ajouter Staticman en tant que collaborateur\nJe 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…\nQuoi 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.\n\nRévoquez les droits de collaboration de Staticman v1 dans les paramètres de votre dépôt GitHub.  \n\n\n\n\n\nAjoutez de nouveau Staticman en tant que collaborateur.\nFaites un appel sur ce endpoint de la version 2 de l’API https:\/\/api.staticman.net\/v2\/connect\/{votre nom d’utilisateur GitHub}\/{nom de votre dépôt} pour accepter l’invitation de collaboration.\n\nMettre à jour l’appel POST du formulaire de commentaires\nPour faire une requête POST correcte à Staticman, l’attribut action de mon formulaire de commentaire avait besoin d’une petite mise à jour. Remplacer v1 par v2 dans _includes\/page__comments.html, puis suffixer avec \/comments2 et le tour était joué pour moi.\n&lt;form\n  id=\"comment-form\"\n  class=\"page__form js-form form\"\n  method=\"post\"\n  action=\"https:\/\/api.staticman.net\/v2\/entry\/{{ site.repository }}\/{{ site.staticman.branch }}\/comments\"\n&gt;&lt;\/form&gt;\nAjout du support des fils de commentaires\nRé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 for à l’intérieur d’autres boucles for, 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.\nAjout d’un identifiant au parent\nPour imbriquer correctement les réponses, j'avais besoin de pouvoir déterminer leur hiérarchie. La v2 de Staticman possède un nouveau champ nommé options[parent]3 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é.\n&lt;input type=\"hidden\" id=\"comment-parent\" name=\"options[parent]\" value=\"\" \/&gt;\nMise à jour des boucles Liquid\nAfin 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 where_exp de Jekyll.\nLe filtre d’expression where de Jekyll\nSélectionne tous les objets d’un tableau pour lesquels la condition est vraie, depuis la version 3.2.0 de Jekyll.\nExemple: site.members | where_exp:\"item\",\"item.graduation_year == 2014\"\nSi le champ caché options[parent] que j'ai ajouté au formulaire fonctionne correctement, je devrais obtenir des fichiers de données de commentaires similaires à ceux-ci :\nExemple de commentaire parent\nmessage: Ceci est le message du commentaire parent\nname: Prénom Nom\nemail: md5g1bb3r15h\ndate: 2016-11-30T22:03:15.286Z\nExemple de commentaire enfant\n_parent: 7\nmessage: Ceci est un message de commentaire enfant\nname: Prénom Nom\nemail: md5g1bb3r15h\ndate: 2016-11-02T05:08:43.280Z\nComme vous pouvez le voir ci-dessus, le commentaire \"enfant\" a une donnée _parent renseignée à partir du champ caché options[parent] du formulaire.\nSachant cela, j'ai tenté d’utiliser where_exp:\"item\",\"item._parent == nil\" pour créer un tableau ne contenant que les commentaires \"parents\".\nMalheureusement, le code suivant n'a pas marché :\n{% assign comments = site.data.comments[page.slug] | where_exp:\"item\",\"item._parent == nil\" %}\n{% for comment in comments %}\n  {% assign avatar = comment[1].avatar %}\n  {% assign email = comment[1].email %}\n  {% assign name = comment[1].name %}\n  {% assign url = comment[1].url %}\n  {% assign date = comment[1].date %}\n  {% assign message = comment[1].message %}\n  {% include comment.html index=forloop.index avatar=avatar email=email name=name url=url date=date message=message %}\n{% endfor %}\nÀ la place, j'ai eu tout un tas de commentaires vides avec le balisage suivant :\n&lt;article id=\"comment-1\" class=\"js-comment comment\"&gt;\n  &lt;div class=\"comment__avatar\"&gt;\n    &lt;img src=\"\" alt=\"\" \/&gt;\n  &lt;\/div&gt;\n  &lt;h3 class=\"comment__author-name\"&gt;&lt;\/h3&gt;\n  &lt;div class=\"comment__timestamp\"&gt;\n    &lt;a href=\"#comment-1\" title=\"Permalien vers ce commentaire\"&gt;\n      &lt;time datetime=\"\"&gt;&lt;\/time&gt;\n    &lt;\/a&gt;\n  &lt;\/div&gt;\n  &lt;div class=\"comment__content\"&gt;&lt;\/div&gt;\n&lt;\/article&gt;\nHmm… j'imagine qu'il était temps d’ajouter des filtres inspect à mes tableaux pour voir ce qui se passait.\n{{ site.data.comments[page.slug] | inspect }}\nExemple de tableau avant filtrage avec where_exp\n{\n  \"comment-1471818805944\" =&gt; {\n    \"message\" =&gt; \"Ceci est un message de commentaire parent.\",\n    \"name\"    =&gt; \"Prénom Nom\",\n    \"email\"   =&gt; \"md5g1bb3r15h\",\n    \"url\"     =&gt; \"\",\n    \"hidden\"  =&gt; \"\",\n    \"date\"    =&gt; \"2016-08-21T22:33:25.272Z\"\n  },\n  \"comment-1471904599908\" =&gt; {\n    \"message\" =&gt; \"Ceci est un autre message de commentaire parent.\",\n    \"name\"    =&gt; \"Prénom Nom\",\n    \"email\"   =&gt; \"md5g1bb3r15h\",\n    \"url\"     =&gt; \"\",\n    \"hidden\"  =&gt; \"\",\n    \"date\"    =&gt; \"2016-08-22T21:42:48.075Z\"\n  }\n}\nExemple de tableau après filtrage avec where_exp\n[\n  {\n    \"message\" =&gt; \"Ceci est un message de commentaire parent.\",\n    \"name\"    =&gt; \"Prénom Nom\",\n    \"email\"   =&gt; \"md5g1bb3r15h\",\n    \"url\"     =&gt; \"\",\n    \"hidden\"  =&gt; \"\",\n    \"date\"    =&gt; \"2016-08-21T22:33:25.272Z\"\n  },\n  {\n    \"message\" =&gt; \"Ceci est un autre message de commentaire parent.\",\n    \"name\"    =&gt; \"Prénom Nom\",\n    \"email\"   =&gt; \"md5g1bb3r15h\",\n    \"url\"     =&gt; \"\",\n    \"hidden\"  =&gt; \"\",\n    \"date\"    =&gt; \"2016-08-22T21:42:48.075Z\"\n  }\n]\nApparemment l’utilisation du filtre where_exp aplatit quelque peu les choses en supprimant les objets comment-xxxxxxxxxxxxx. Cela fait que mes tags assign retournent des valeurs nulles parce que comment[1] n'existe plus.\n{% assign avatar  = comment[1].avatar %}\n{% assign email   = comment[1].email %}\n{% assign name    = comment[1].name %}\n{% assign url     = comment[1].url %}\n{% assign date    = comment[1].date %}\n{% assign message = comment[1].message %}\nUne fois cela découvert, la solution était simple --- supprimer [1] pour chacun des noms des propriétés.\n{% assign avatar  = comment.avatar %}\n{% assign email   = comment.email %}\n{% assign name    = comment.name %}\n{% assign url     = comment.url %}\n{% assign date    = comment.date %}\n{% assign message = comment.message %}\n\n\n\n\n\n\nÇa marche, nous avons des commentaires parents.\n\nNote : sort et les filtres where ne font pas bon ménage\nJe suis tombé sur des comportements étranges et des erreurs dus à l’utilisation du filtre de tri sort avec les filtres de recherche where et where_exp. 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.\nJ'utilise le format suivant : filename: \\\"comment-{@timestamp}\\\". Tout dépend donc de comment vous nommez vos fichiers de commentaires.\nAfficher les commentaires imbriqués\nVoici ce que je cherchais à accomplir… avant que le mal de tête ne commence\n😧 🔫\n\nDéclarer une boucle et, à chaque itération, créer un nouveau tableau nommé replies ne contenant que les réponses aux commentaires.\nÉvaluer la valeur de _parent pour ces réponses.\nSi _parent est égal à l’index de la boucle parente alors il doit être traité comme un commentaire \"enfant\".\nSinon, on passe à l’entrée suivante du tableau.\nEt ainsi de suite.\n\nJ'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.\nHeureusement Liquid nous permet de faire cela à l’aide de forloop.index.\n{% assign index = forloop.index %}\nEnsuite j'ai imbriqué une copie modifiée de la boucle parent précédente à l’intérieur d’elle-même --- pour faire fonction de boucle \"enfant\" ou replies.\n{% assign replies = site.data.comments[page.slug] | where_exp:\"item\",\"item._parent == include.index\" %}\n{% for reply in replies %}\n  {% assign parent  = reply._parent %}\n  {% assign avatar  = reply.avatar %}\n  {% assign email   = reply.email %}\n  {% assign name    = reply.name %}\n  {% assign url     = reply.url %}\n  {% assign date    = reply.date %}\n  {% assign message = reply.message %}\n  {% include comment.html parent=parent avatar=avatar email=email name=name url=url date=date message=message %}\n{% endfor %}\nMalheureusement le filtre where_exp s'est révélé problématique une fois de plus, obligeant Jekyll à générer l’erreur suivante :\nLiquid Exception: Liquid error (line 47): Nesting too deep in \/_layouts\/page.html.\nAprès avoir brièvement songé un moment au film Inception, j'ai appliqué un filtre inspect pour m'aider à m'en sortir avec la boucle replies. J'en ai conclu que la condition where_exp échouait4 parce que je tentais de comparer un entier avec une chaîne de caractères 😳.\nPour résoudre cela, j'ai placé une balise capture autour de la variable d’index pour la convertir en chaîne de caractères. Puis j'ai modifié la condition du filtre where_exp afin de comparer _parent avec cette nouvelle variable {{ i }} --- pour corriger le problème et me permettre de passer à la suite.\n{% capture i %}{{ include.index }}{% endcapture %}\n{% assign replies = site.data.comments[page.slug] | where_exp:\"item\",\"item._parent == i\" %}\n_includes\/page__comments.html\n&lt;section class=\"page__reactions\"&gt;\n  {% if site.repository and site.staticman.branch %}\n    {% if site.data.comments[page.slug] %}\n      &lt;!-- Start static comments --&gt;\n      &lt;div id=\"comments\" class=\"js-comments\"&gt;\n        &lt;h2 class=\"page__section-label\"&gt;\n          {% if site.data.comments[page.slug].size &gt; 1 %}\n            {{ site.data.comments[page.slug] | size }}\n          {% endif %}\n          Comments\n        &lt;\/h2&gt;\n        {% assign comments = site.data.comments[page.slug] | sort | where_exp: 'comment', 'comment[1].replying_to == blank' %}\n        {% for comment in comments %}\n          {% assign index       = forloop.index %}\n          {% assign replying_to = comment[1].replying_to | to_integer %}\n          {% assign avatar      = comment[1].avatar %}\n          {% assign email       = comment[1].email %}\n          {% assign name        = comment[1].name %}\n          {% assign url         = comment[1].url %}\n          {% assign date        = comment[1].date %}\n          {% assign message     = comment[1].message %}\n          {% include comment.html index=index replying_to=replying_to avatar=avatar email=email name=name url=url date=date message=message %}\n        {% endfor %}\n      &lt;\/div&gt;\n      &lt;!-- End static comments --&gt;\n    {% endif %}\n\n    {% unless page.comments_locked == true %}\n      &lt;!-- Start new comment form --&gt;\n      &lt;div id=\"respond\"&gt;\n        &lt;h2 class=\"page__section-label\"&gt;Leave a Comment &lt;small&gt;&lt;a rel=\"nofollow\" id=\"cancel-comment-reply-link\" href=\"{{ page.url | absolute_url }}#respond\" style=\"display:none;\"&gt;Cancel reply&lt;\/a&gt;&lt;\/small&gt;&lt;\/h2&gt;\n        &lt;form id=\"comment-form\" class=\"page__form js-form form\" method=\"post\" action=\"https:\/\/api.staticman.net\/v2\/entry\/{{ site.repository }}\/{{ site.staticman.branch }}\/comments\"&gt;\n          &lt;fieldset&gt;\n            &lt;label for=\"comment-form-message\"&gt;&lt;strong&gt;Comment&lt;\/strong&gt; &lt;small&gt;(&lt;a href=\"https:\/\/kramdown.gettalong.org\/quickref.html\"&gt;Markdown&lt;\/a&gt; is allowed)&lt;\/small&gt;&lt;\/label&gt;\n            &lt;textarea type=\"text\" rows=\"6\" id=\"comment-form-message\" name=\"fields[message]\" required spellcheck=\"true\"&gt;&lt;\/textarea&gt;\n          &lt;\/fieldset&gt;\n          &lt;fieldset&gt;\n            &lt;label for=\"comment-form-name\"&gt;&lt;strong&gt;Name&lt;\/strong&gt;&lt;\/label&gt;\n            &lt;input type=\"text\" id=\"comment-form-name\" name=\"fields[name]\" required spellcheck=\"false\"&gt;\n          &lt;\/fieldset&gt;\n          &lt;fieldset&gt;\n            &lt;label for=\"comment-form-email\"&gt;&lt;strong&gt;Email&lt;\/strong&gt; &lt;small&gt;(used for &lt;a href=\"http:\/\/gravatar.com\"&gt;Gravatar&lt;\/a&gt; image and reply notifications)&lt;\/small&gt;&lt;\/label&gt;\n            &lt;input type=\"email\" id=\"comment-form-email\" name=\"fields[email]\" required spellcheck=\"false\"&gt;\n          &lt;\/fieldset&gt;\n          &lt;fieldset&gt;\n            &lt;label for=\"comment-form-url\"&gt;&lt;strong&gt;Website&lt;\/strong&gt; &lt;small&gt;(optional)&lt;\/small&gt;&lt;\/label&gt;\n            &lt;input type=\"url\" id=\"comment-form-url\" name=\"fields[url]\"\/&gt;\n          &lt;\/fieldset&gt;\n          &lt;fieldset class=\"hidden\" style=\"display: none;\"&gt;\n            &lt;input type=\"hidden\" name=\"options[origin]\" value=\"{{ page.url | absolute_url }}\"&gt;\n            &lt;input type=\"hidden\" name=\"options[parent]\" value=\"{{ page.url | absolute_url }}\"&gt;\n            &lt;input type=\"hidden\" id=\"comment-replying-to\" name=\"fields[replying_to]\" value=\"\"&gt;\n            &lt;input type=\"hidden\" id=\"comment-post-id\" name=\"options[slug]\" value=\"{{ page.slug }}\"&gt;\n            &lt;label for=\"comment-form-location\"&gt;Leave blank if you are a human&lt;\/label&gt;\n            &lt;input type=\"text\" id=\"comment-form-location\" name=\"fields[hidden]\" autocomplete=\"off\"&gt;\n          &lt;\/fieldset&gt;\n          &lt;!-- Start comment form alert messaging --&gt;\n          &lt;p class=\"hidden js-notice\"&gt;\n            &lt;span class=\"js-notice-text\"&gt;&lt;\/span&gt;\n          &lt;\/p&gt;\n          &lt;!-- End comment form alert messaging --&gt;\n          &lt;fieldset&gt;\n            &lt;label for=\"comment-form-reply\"&gt;\n              &lt;input type=\"checkbox\" id=\"comment-form-reply\" name=\"options[subscribe]\" value=\"email\"&gt;\n              Notify me of replies by email.\n            &lt;\/label&gt;\n            &lt;button type=\"submit\" id=\"comment-form-submit\" class=\"btn btn--large\"&gt;Submit Comment&lt;\/button&gt;\n          &lt;\/fieldset&gt;\n        &lt;\/form&gt;\n      &lt;\/div&gt;\n      &lt;!-- End new comment form --&gt;\n    {% else %}\n      &lt;p&gt;&lt;em&gt;Comments are closed. If you have a question concerning the content of this page, please feel free to &lt;a href=\"\/contact\/\"&gt;contact me&lt;\/a&gt;.&lt;\/em&gt;&lt;\/p&gt;\n    {% endunless %}\n  {% endif %}\n&lt;\/section&gt;\n_includes\/comment.html\n&lt;article id=\"comment{% unless include.r %}{{ index | prepend: '-' }}{% else %}{{ include.index | prepend: '-' }}{% endunless %}\" class=\"js-comment comment {% if include.name == site.author.name %}admin{% endif %} {% unless include.replying_to == 0 %}child{% endunless %}\"&gt;\n  &lt;div class=\"comment__avatar\"&gt;\n    {% if include.avatar %}\n      &lt;img src=\"{{ include.avatar }}\" alt=\"{{ include.name | escape }}\"&gt;\n    {% elsif include.email %}\n      &lt;img src=\"https:\/\/www.gravatar.com\/avatar\/{{ include.email }}?d=mm&amp;s=60\" srcset=\"https:\/\/www.gravatar.com\/avatar\/{{ include.email }}?d=mm&amp;s=120 2x\" alt=\"{{ include.name | escape }}\"&gt;\n    {% else %}\n      &lt;img src=\"\/assets\/images\/avatar-60.png\" srcset=\"\/assets\/images\/avatar-120.png 2x\" alt=\"{{ include.name | escape }}\"&gt;\n    {% endif %}\n  &lt;\/div&gt;\n  &lt;h3 class=\"comment__author-name\"&gt;\n    {% unless include.url == blank %}\n      &lt;a rel=\"external nofollow\" href=\"{{ include.url }}\"&gt;\n        {% if include.name == site.author.name %}&lt;svg class=\"icon\" width=\"20px\" height=\"20px\"&gt;&lt;use xlink:href=\"#icon-mistake\"&gt;&lt;\/use&gt;&lt;\/svg&gt; {% endif %}{{ include.name }}\n      &lt;\/a&gt;\n    {% else %}\n      {% if include.name == site.author.name %}&lt;svg class=\"icon\" width=\"20px\" height=\"20px\"&gt;&lt;use xlink:href=\"#icon-mistake\"&gt;&lt;\/use&gt;&lt;\/svg&gt; {% endif %}{{ include.name }}\n    {% endunless %}\n  &lt;\/h3&gt;\n  &lt;div class=\"comment__timestamp\"&gt;\n    {% if include.date %}\n      {% if include.index %}&lt;a href=\"#comment{% if r %}{{ index | prepend: '-' }}{% else %}{{ include.index | prepend: '-' }}{% endif %}\" title=\"path to this comment\"&gt;{% endif %}\n      &lt;time datetime=\"{{ include.date | date_to_xmlschema }}\"&gt;{{ include.date | date: '%B %d, %Y' }}&lt;\/time&gt;\n      {% if include.index %}&lt;\/a&gt;{% endif %}\n    {% endif %}\n  &lt;\/div&gt;\n  &lt;div class=\"comment__content\"&gt;\n    {{ include.message | markdownify }}\n  &lt;\/div&gt;\n  {% unless include.replying_to != 0 or page.comments_locked == true %}\n    &lt;div class=\"comment__reply\"&gt;\n      &lt;a rel=\"nofollow\" class=\"btn\" href=\"#comment-{{ include.index }}\" onclick=\"return addComment.moveForm('comment-{{ include.index }}', '{{ include.index }}', 'respond', '{{ page.slug }}')\"&gt;Reply to {{ include.name }}&lt;\/a&gt;\n    &lt;\/div&gt;\n  {% endunless %}\n&lt;\/article&gt;\n\n{% capture i %}{{ include.index }}{% endcapture %}\n{% assign replies = site.data.comments[page.slug] | sort | where_exp: 'comment', 'comment[1].replying_to == i' %}\n{% for reply in replies %}\n  {% assign index       = forloop.index | prepend: '-' | prepend: include.index %}\n  {% assign replying_to = reply[1].replying_to | to_integer %}\n  {% assign avatar      = reply[1].avatar %}\n  {% assign email       = reply[1].email %}\n  {% assign name        = reply[1].name %}\n  {% assign url         = reply[1].url %}\n  {% assign date        = reply[1].date %}\n  {% assign message     = reply[1].message %}\n  {% include comment.html index=index replying_to=replying_to avatar=avatar email=email name=name url=url date=date message=message %}\n{% endfor %}\nHTML et JavaScript pour la réponse à un commentaire\nL'étape suivante a consisté à ajouter quelques touches finales pour que le tout fonctionne.\nHabitué à la manière dont WordPress 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 wp-includes\/js\/comment-reply.js j'ai trouvé tout ce dont j'avais besoin :\n\nune fonction respond pour déplacer le formulaire dans la vue,\nune fonction cancel pour supprimer un formulaire de réponse et le repositionner à son état d’origine,\npasser l’identifiant unique du parent à options[parent] lors de la soumission du formulaire.\n\nJ'ai commencé par utiliser une condition unless pour n'afficher que les liens \"répondre\" sur les commentaires parents. 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.\n{% unless r %}\n  &lt;div class=\"comment__reply\"&gt;\n    &lt;a rel=\"nofollow\" class=\"btn\" href=\"#comment-{{ include.index }}\"&gt;\n      Reply to {{ include.name }}\n    &lt;\/a&gt;\n  &lt;\/div&gt;\n{% endunless %}\n\n\n\n\n\n\nCommentaires imbriqués sur un seul niveau de profondeur.\n\nPour donner vie au lien répondre j'ai lui ai ajouté l’attribut onclick suivant et du JavaScript.\nonclick = \"return addComment.moveForm('comment-{{ include.index }}', '{{ include.index }}', 'respond’, '{{ page.slug }}')\";\nJ'ai juste eu à modifier quelques noms de variables dans le script comment-reply.js de WordPress pour que tout marche bien avec le balisage de mon formulaire.\nAjout du support des notifications par mail\nComparées aux réponses de commentaires imbriqués, les notifications par mail\nfurent très simples à mettre en place.\nMise à jour de la configuration staticman.yml\nPour s'assurer que les liens dans les mails de notifications sont sûrs et ne\nproviennent que de domaines de confiance, définissez allowedOrigins en\nfonction.\nExemple :\nallowedOrigins: [\"mademistakes.com\"]\nLe(s) domaine(s) autorisé()s doi(ven)t correspondre à ceux passés par le champ options.origin que nous allons ajouter à la prochaine étape. Seuls les domaines correspondants déclencheront les notifications à envoyer, faute de quoi l’opération échouera.\nProTip : Utilisez votre propre compte Mailgun\nL'instance publique de Static man utilise un compte Mailgun limité à 10 000 emails par mois. Je\nvous encourage à créer un compte et à ajouter votre propre API et domaine Mailgun dans le fichier staticman.yml. Assurez-vous de bien chiffrer les deux en utilisant le chemin suivant : https:\/\/api.staticman.net\/v2\/encrypt\/{TEXTE À CHIFFRER}.\nMise à jour du formulaire de commentaire\nPour terminer, ajoutons deux champs au formulaire de commentaire.\nChamp 1 : Un champ caché qui passe la valeur d’origin5 défini dans le fichier staticman.yml:\n&lt;input type=\"hidden\" name=\"options[origin]\" value=\"{{ page.url | absolute_url }}\"&gt;\nChamp 2 : Un input de type case à cocher pour s'inscrire aux notifications par mail.\n&lt;label for=\"comment-form-reply\"&gt;\n  &lt;input type=\"checkbox\" id=\"comment-form-reply\" name=\"options[subscribe]\" value=\"email\"&gt;\n  Me prévenir par mail des nouveaux commentaires.\n&lt;\/label&gt;\nRien de bien surprenant ici, name=options[subscribe] and value=\"email\" sont ajoutés au champ pour associer les données d’abonnement avec l’adresse mail.\nSi 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é.\n\n\n\n\n\n\nExemple d’un mail de notification « Nouvelle réponse » de Staticman.\n\nVoilà, 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 😦.\n\n\n\n\nUn des avantages du nouveau fichier de configuration c'est qu'on peut utiliser Staticman avec d’autres générateurs de site statique. La v2 ne vous oblige plus à utiliser un fichier _config.yml spécifique à Jekyll.&#160;&#8617;\n\n\nLes propriétés de site sont optionnelles. Se reporter à la documentation de Staticman pour plus de détails sur comment connecter vos formulaires.&#160;&#8617;\n\n\nStaticman nomme ce champ _parent dans les entrées.&#160;&#8617;\n\n\n15 n'est pas la même chose que '15'. Ces guillemets simples font toute la différence…&#160;&#8617;\n\n\nCette URL sera ajoutée dans la notification par mail envoyée aux abonnés pour leur permettre d’ouvrir directement la page.&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<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>\n<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>\n<h2 id=\"passer-a-la-version-2-de-staticman\">Passer à la version 2 de Staticman</h2>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">comments:</span>\n  <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>\n  <span class=\"hljs-attr\">branch            :</span> <span class=\"hljs-string\">\"master\"</span>\n  <span class=\"hljs-attr\">commitMessage     :</span> <span class=\"hljs-string\">\"Nouveau commentaire.\"</span>\n  <span class=\"hljs-attr\">filename          :</span> <span class=\"hljs-string\">\"commentaire-{@timestamp}\"</span>\n  <span class=\"hljs-attr\">format            :</span> <span class=\"hljs-string\">\"yaml\"</span>\n  <span class=\"hljs-attr\">moderation        :</span> <span class=\"hljs-literal\">true</span>\n  <span class=\"hljs-attr\">path              :</span> <span class=\"hljs-string\">\"src/_data/comments/{options.slug}\"</span>\n  <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>\n  <span class=\"hljs-attr\">transforms:</span>\n    <span class=\"hljs-attr\">email           :</span> <span class=\"hljs-string\">md5</span>\n  <span class=\"hljs-attr\">generatedFields:</span>\n    <span class=\"hljs-attr\">date:</span>\n      <span class=\"hljs-attr\">type          :</span> <span class=\"hljs-string\">\"date\"</span>\n      <span class=\"hljs-attr\">options:</span>\n        <span class=\"hljs-attr\">format      :</span> <span class=\"hljs-string\">\"iso8601\"</span></code></pre>\n<h3 id=\"nouvelles-options-de-configuration\">Nouvelles options de configuration</h3>\n<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>\n<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>\n<h3 id=\"supprimer-ajouter-staticman-en-tant-que-collaborateur\">Supprimer/Ajouter Staticman en tant que collaborateur</h3>\n<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>\n<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>\n<ol>\n<li>Révoquez les droits de collaboration de Staticman <code>v1</code> dans les paramètres de votre dépôt GitHub.  <picture>\n<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\">\n<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\">\n<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\">\n</picture>\n</li>\n<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>\n<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>\n</ol>\n<h3 id=\"mettre-a-jour-l-appel-post-du-formulaire-de-commentaires\">Mettre à jour l’appel POST du formulaire de commentaires</h3>\n<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>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form</span>\n  <span class=\"hljs-attr\">id</span>=<span class=\"hljs-string\">\"comment-form\"</span>\n  <span class=\"hljs-attr\">class</span>=<span class=\"hljs-string\">\"page__form js-form form\"</span>\n  <span class=\"hljs-attr\">method</span>=<span class=\"hljs-string\">\"post\"</span>\n  <span class=\"hljs-attr\">action</span>=<span class=\"hljs-string\">\"https://api.staticman.net/v2/entry/{{ site.repository }}/{{ site.staticman.branch }}/comments\"</span>\n&gt;</span><span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">form</span>&gt;</span></code></pre>\n<h2 id=\"ajout-du-support-des-fils-de-commentaires\">Ajout du support des fils de commentaires</h2>\n<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>\n<h3 id=\"ajout-d-un-identifiant-au-parent\">Ajout d’un identifiant au parent</h3>\n<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>\n<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>\n<h3 id=\"mise-a-jour-des-boucles-liquid\">Mise à jour des boucles Liquid</h3>\n<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>\n<aside class=\"note note-tip\"><h4 id=\"le-filtre-d-expression-where-de-jekyll\">Le filtre d’expression where de Jekyll</h4>\n<p>Sélectionne tous les objets d’un tableau pour lesquels la condition est vraie, depuis la version 3.2.0 de Jekyll.\n<strong>Exemple:</strong> <code>site.members | where_exp:\"item\",\"item.graduation_year == 2014\"</code></p></aside>\n<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>\n<h4 id=\"exemple-de-commentaire-parent\">Exemple de commentaire parent</h4>\n<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>\n<span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Prénom</span> <span class=\"hljs-string\">Nom</span>\n<span class=\"hljs-attr\">email:</span> <span class=\"hljs-string\">md5g1bb3r15h</span>\n<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>\n<h4 id=\"exemple-de-commentaire-enfant\">Exemple de commentaire enfant</h4>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">_parent:</span> <span class=\"hljs-number\">7</span>\n<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>\n<span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Prénom</span> <span class=\"hljs-string\">Nom</span>\n<span class=\"hljs-attr\">email:</span> <span class=\"hljs-string\">md5g1bb3r15h</span>\n<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>\n<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>\nSachant 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>\n<p>Malheureusement, le code suivant n'a pas marché :</p>\n<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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">for</span></span> comment in comments %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> avatar = comment[1].avatar %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> email = comment[1].email %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> name = comment[1].name %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> url = comment[1].url %}</span><span class=\"xml\">\n  </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\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> message = comment[1].message %}</span><span class=\"xml\">\n  </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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<p>À la place, j'ai eu tout un tas de commentaires vides avec le balisage suivant :</p>\n<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>\n  <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>\n    <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>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <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>\n  <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>\n    <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>\n      <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>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">a</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <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>\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">article</span>&gt;</span></code></pre>\n<p>Hmm… j'imagine qu'il était temps d’ajouter des filtres <code>inspect</code> à mes tableaux pour voir ce qui se passait.</p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"hljs-template-variable\">{{ site.data.comments[page.slug] | inspect }}</span></code></pre>\n<h4 id=\"exemple-de-tableau-avant-filtrage-avec-where-exp\">Exemple de tableau avant filtrage avec <code>where_exp</code></h4>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-string\">{</span>\n  <span class=\"hljs-string\">\"comment-1471818805944\"</span> <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">{</span>\n    <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>\n    <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>\n    <span class=\"hljs-string\">\"email\"</span>   <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">\"md5g1bb3r15h\"</span><span class=\"hljs-string\">,</span>\n    <span class=\"hljs-string\">\"url\"</span>     <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">\"\"</span><span class=\"hljs-string\">,</span>\n    <span class=\"hljs-string\">\"hidden\"</span>  <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">\"\"</span><span class=\"hljs-string\">,</span>\n    <span class=\"hljs-string\">\"date\"</span>    <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">\"2016-08-21T22:33:25.272Z\"</span>\n  <span class=\"hljs-string\">},</span>\n  <span class=\"hljs-string\">\"comment-1471904599908\"</span> <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">{</span>\n    <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>\n    <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>\n    <span class=\"hljs-string\">\"email\"</span>   <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">\"md5g1bb3r15h\"</span><span class=\"hljs-string\">,</span>\n    <span class=\"hljs-string\">\"url\"</span>     <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">\"\"</span><span class=\"hljs-string\">,</span>\n    <span class=\"hljs-string\">\"hidden\"</span>  <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">\"\"</span><span class=\"hljs-string\">,</span>\n    <span class=\"hljs-string\">\"date\"</span>    <span class=\"hljs-string\">=&gt;</span> <span class=\"hljs-string\">\"2016-08-22T21:42:48.075Z\"</span>\n  <span class=\"hljs-string\">}</span>\n<span class=\"hljs-string\">}</span></code></pre>\n<h4 id=\"exemple-de-tableau-apres-filtrage-avec-where-exp\">Exemple de tableau après filtrage avec <code>where_exp</code></h4>\n<pre><code class=\"language-json hljs json\">[\n  {\n    <span class=\"hljs-attr\">\"message\"</span> =&gt; <span class=\"hljs-attr\">\"Ceci est un message de commentaire parent.\"</span>,\n    <span class=\"hljs-attr\">\"name\"</span>    =&gt; <span class=\"hljs-attr\">\"Prénom Nom\"</span>,\n    <span class=\"hljs-attr\">\"email\"</span>   =&gt; <span class=\"hljs-attr\">\"md5g1bb3r15h\"</span>,\n    <span class=\"hljs-attr\">\"url\"</span>     =&gt; <span class=\"hljs-attr\">\"\"</span>,\n    <span class=\"hljs-attr\">\"hidden\"</span>  =&gt; <span class=\"hljs-attr\">\"\"</span>,\n    <span class=\"hljs-attr\">\"date\"</span>    =&gt; <span class=\"hljs-attr\">\"2016-08-21T22:33:25.272Z\"</span>\n  },\n  {\n    <span class=\"hljs-attr\">\"message\"</span> =&gt; <span class=\"hljs-attr\">\"Ceci est un autre message de commentaire parent.\"</span>,\n    <span class=\"hljs-attr\">\"name\"</span>    =&gt; <span class=\"hljs-attr\">\"Prénom Nom\"</span>,\n    <span class=\"hljs-attr\">\"email\"</span>   =&gt; <span class=\"hljs-attr\">\"md5g1bb3r15h\"</span>,\n    <span class=\"hljs-attr\">\"url\"</span>     =&gt; <span class=\"hljs-attr\">\"\"</span>,\n    <span class=\"hljs-attr\">\"hidden\"</span>  =&gt; <span class=\"hljs-attr\">\"\"</span>,\n    <span class=\"hljs-attr\">\"date\"</span>    =&gt; <span class=\"hljs-attr\">\"2016-08-22T21:42:48.075Z\"</span>\n  }\n]</code></pre>\n<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>\n<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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> email   = comment[1].email %}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> name    = comment[1].name %}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> url     = comment[1].url %}</span><span class=\"xml\">\n</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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> message = comment[1].message %}</span></code></pre>\n<p>Une fois cela découvert, la solution était simple --- supprimer <code>[1]</code> pour chacun des noms des propriétés.</p>\n<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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> email   = comment.email %}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> name    = comment.name %}</span><span class=\"xml\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> url     = comment.url %}</span><span class=\"xml\">\n</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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> message = comment.message %}</span></code></pre>\n<figure>\n<picture title=\"Ça marche, nous avons des commentaires parents.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Ça marche, nous avons des commentaires parents.</figcaption>\n</figure>\n<aside class=\"note note-note\"><p><strong>Note : <code>sort</code> et les filtres <code>where</code> ne font pas bon ménage</strong>\nJe 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.\nJ'utilise le format suivant : <code>filename: \\\"comment-{@timestamp}\\\"</code>. Tout dépend donc de comment vous nommez vos fichiers de commentaires.</p></aside>\n<h4 id=\"afficher-les-commentaires-imbriques\">Afficher les commentaires imbriqués</h4>\n<p>Voici ce que je cherchais à accomplir… avant que le mal de tête ne commence\n😧 🔫</p>\n<ul>\n<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>\n<li>Évaluer la valeur de <code>_parent</code> pour ces réponses.</li>\n<li>Si <code>_parent</code> est égal à l’index de la boucle parente alors il doit être traité comme un commentaire \"enfant\".</li>\n<li>Sinon, on passe à l’entrée suivante du tableau.</li>\n<li>Et ainsi de suite.</li>\n</ul>\n<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>\nHeureusement Liquid nous permet de faire cela à l’aide de <code>forloop.index</code>.</p>\n<pre><code class=\"language-twig hljs twig\"><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> index = forloop.index %}</span></code></pre>\n<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>\n<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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">for</span></span> reply in replies %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> <span class=\"hljs-name\">parent</span>  = reply._parent %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> avatar  = reply.avatar %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> email   = reply.email %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> name    = reply.name %}</span><span class=\"xml\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> url     = reply.url %}</span><span class=\"xml\">\n  </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\">\n  </span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\">assign</span> message = reply.message %}</span><span class=\"xml\">\n  </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\">\n</span><span class=\"hljs-template-tag\">{% <span class=\"hljs-name\"><span class=\"hljs-keyword\">endfor</span></span> %}</span></code></pre>\n<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>\n<code>Liquid Exception: Liquid error (line 47): Nesting too deep in /_layouts/page.html</code>.</p>\n<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>\n<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>\n<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\">\n</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>\n<h4 id=\"includes-page-comments-html\"><code>_includes/page__comments.html</code></h4>\n<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>\n  {% if site.repository and site.staticman.branch %}\n    {% if site.data.comments[page.slug] %}\n      <span class=\"hljs-comment\">&lt;!-- Start static comments --&gt;</span>\n      <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>\n        <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>\n          {% if site.data.comments[page.slug].size &gt; 1 %}\n            {{ site.data.comments[page.slug] | size }}\n          {% endif %}\n          Comments\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">h2</span>&gt;</span>\n        {% assign comments = site.data.comments[page.slug] | sort | where_exp: 'comment', 'comment[1].replying_to == blank' %}\n        {% for comment in comments %}\n          {% assign index       = forloop.index %}\n          {% assign replying_to = comment[1].replying_to | to_integer %}\n          {% assign avatar      = comment[1].avatar %}\n          {% assign email       = comment[1].email %}\n          {% assign name        = comment[1].name %}\n          {% assign url         = comment[1].url %}\n          {% assign date        = comment[1].date %}\n          {% assign message     = comment[1].message %}\n          {% include comment.html index=index replying_to=replying_to avatar=avatar email=email name=name url=url date=date message=message %}\n        {% endfor %}\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n      <span class=\"hljs-comment\">&lt;!-- End static comments --&gt;</span>\n    {% endif %}\n\n    {% unless page.comments_locked == true %}\n      <span class=\"hljs-comment\">&lt;!-- Start new comment form --&gt;</span>\n      <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>\n        <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>\n        <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>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">fieldset</span>&gt;</span>\n            <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>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">fieldset</span>&gt;</span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">fieldset</span>&gt;</span>\n            <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>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">fieldset</span>&gt;</span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">fieldset</span>&gt;</span>\n            <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>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">fieldset</span>&gt;</span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">fieldset</span>&gt;</span>\n            <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>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">fieldset</span>&gt;</span>\n          <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>\n            <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>\n            <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>\n            <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>\n            <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>\n            <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>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">fieldset</span>&gt;</span>\n          <span class=\"hljs-comment\">&lt;!-- Start comment form alert messaging --&gt;</span>\n          <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>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">p</span>&gt;</span>\n          <span class=\"hljs-comment\">&lt;!-- End comment form alert messaging --&gt;</span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">fieldset</span>&gt;</span>\n            <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>\n              <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>\n              Notify me of replies by email.\n            <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">label</span>&gt;</span>\n            <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>\n          <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">fieldset</span>&gt;</span>\n        <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">form</span>&gt;</span>\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n      <span class=\"hljs-comment\">&lt;!-- End new comment form --&gt;</span>\n    {% else %}\n      <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>\n    {% endunless %}\n  {% endif %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">section</span>&gt;</span></code></pre>\n<h4 id=\"includes-comment-html\"><code>_includes/comment.html</code></h4>\n<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>\n  <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>\n    {% if include.avatar %}\n      <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>\n    {% elsif include.email %}\n      <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>\n    {% else %}\n      <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>\n    {% endif %}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <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>\n    {% unless include.url == blank %}\n      <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>\n        {% 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 }}\n      <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">a</span>&gt;</span>\n    {% else %}\n      {% 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 }}\n    {% endunless %}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">h3</span>&gt;</span>\n  <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>\n    {% if include.date %}\n      {% 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 %}\n      <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>\n      {% if include.index %}<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">a</span>&gt;</span>{% endif %}\n    {% endif %}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  <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>\n    {{ include.message | markdownify }}\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  {% unless include.replying_to != 0 or page.comments_locked == true %}\n    <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>\n      <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>\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n  {% endunless %}\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">article</span>&gt;</span>\n\n{% capture i %}{{ include.index }}{% endcapture %}\n{% assign replies = site.data.comments[page.slug] | sort | where_exp: 'comment', 'comment[1].replying_to == i' %}\n{% for reply in replies %}\n  {% assign index       = forloop.index | prepend: '-' | prepend: include.index %}\n  {% assign replying_to = reply[1].replying_to | to_integer %}\n  {% assign avatar      = reply[1].avatar %}\n  {% assign email       = reply[1].email %}\n  {% assign name        = reply[1].name %}\n  {% assign url         = reply[1].url %}\n  {% assign date        = reply[1].date %}\n  {% assign message     = reply[1].message %}\n  {% include comment.html index=index replying_to=replying_to avatar=avatar email=email name=name url=url date=date message=message %}\n{% endfor %}</code></pre>\n<h3 id=\"html-et-javascript-pour-la-reponse-a-un-commentaire\">HTML et JavaScript pour la réponse à un commentaire</h3>\n<p>L'étape suivante a consisté à ajouter quelques touches finales pour que le tout fonctionne.</p>\n<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>\n<ul>\n<li>une fonction <code>respond</code> pour déplacer le formulaire dans la vue,</li>\n<li>une fonction <code>cancel</code> pour supprimer un formulaire de réponse et le repositionner à son état d’origine,</li>\n<li>passer l’identifiant unique du parent à <code>options[parent]</code> lors de la soumission du formulaire.</li>\n</ul>\n<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>\n<pre><code class=\"language-html hljs xml\">{% unless r %}\n  <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>\n    <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>\n      Reply to {{ include.name }}\n    <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">a</span>&gt;</span>\n  <span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">div</span>&gt;</span>\n{% endunless %}</code></pre>\n<figure>\n<picture title=\"Commentaires imbriqués sur un seul niveau de profondeur.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Commentaires imbriqués sur un seul niveau de profondeur.</figcaption>\n</figure>\n<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>\n<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>\n<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>\n<h2 id=\"ajout-du-support-des-notifications-par-mail\">Ajout du support des notifications par mail</h2>\n<p>Comparées aux réponses de commentaires imbriqués, les notifications par mail\nfurent très simples à mettre en place.</p>\n<h3 id=\"mise-a-jour-de-la-configuration-staticman-yml\">Mise à jour de la configuration <code>staticman.yml</code></h3>\n<p>Pour s'assurer que les liens dans les mails de notifications sont sûrs et ne\nproviennent que de domaines de confiance, définissez <code>allowedOrigins</code> en\nfonction.</p>\n<p><strong>Exemple :</strong></p>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">allowedOrigins:</span> <span class=\"hljs-string\">[\"mademistakes.com\"]</span></code></pre>\n<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>\n<aside class=\"note note-tip\"><h4 id=\"protip-utilisez-votre-propre-compte-mailgun\">ProTip : Utilisez votre propre compte Mailgun</h4>\n<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\nvous 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>\n<h3 id=\"mise-a-jour-du-formulaire-de-commentaire\">Mise à jour du formulaire de commentaire</h3>\n<p>Pour terminer, ajoutons deux champs au formulaire de commentaire.</p>\n<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>\n<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>\n<p><strong>Champ 2 :</strong> Un <code>input</code> de type case à cocher pour s'inscrire aux notifications par mail.</p>\n<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>\n  <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>\n  Me prévenir par mail des nouveaux commentaires.\n<span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">label</span>&gt;</span></code></pre>\n<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>\n<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>\n<figure>\n<picture title=\"Exemple d’un mail de notification « Nouvelle réponse » de Staticman.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Exemple d’un mail de notification « Nouvelle réponse » de Staticman.</figcaption>\n</figure>\n<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>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:staticman-yml\">\n<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>\n</li>\n<li id=\"fn:property\">\n<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>\n</li>\n<li id=\"fn:parent-field\">\n<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>\n</li>\n<li id=\"fn:integer-string\">\n<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>\n</li>\n<li id=\"fn:origin\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/11/10/designer-un-portfolio-avec-jekyll/",
      "url": "https://jamstatic.fr/2016/11/10/designer-un-portfolio-avec-jekyll/",
      "title": "Process de design d’un portfolio",
      "summary": "Les différentes étapes pour concevoir son portfolio avec Jekyll et GitHub Pages par Kat Fukui, webdesigner chez Github.",
      "date_published": "2016-11-10T00:00:00+00:00","content_text": "The Design Portfolio Workflow 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 développer des thèmes pour Jekyll 😊\nUn guide complet pour coder et déployer un site, tout en ligne de commande\nDonc j'étais en train de mettre à jour mon portfolio, 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 James m'a suggéré de tester Jekyll. Mon épopée commence donc ici.\nJe 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 ?\nJ'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 !\n\n\n\n\n\n\nCe type, OK ?\n\nC’est quoi Jekyll ?\nJekyll 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.\n\n\nLiz, j'ai été là-bas et c'est incroyable.\n\nRapide ✔ Propre ✔ Simple ✔ Complet ✔\nOK, tu m’intéresses.\nDéveloppons un portfolio !\nC’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.\n\n\n\n\n\n\nLe terminal\n\nMê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 ici ou ici. Sinon, le copier-coller n'est pas une si mauvaise chose pour commencer à apprendre.\nInstallons Jekyll1 ! Dans le terminal, j'ai tapé :\ngem install jekyll bundler\nPar défaut, le terminal s'ouvre dans votre dossier utilisateur. Vous pouvez voir les fichiers et les dossiers du répertoire courant en entrant ls. Ça me va très bien de créer mon nouveau portfolio à cet endroit, donc je vais taper :\njekyll new mon-site\ncd mon-site\njekyll serve\nExcellent ! J'ai donc maintenant un dossier sur mon ordinateur nommé mon-site.\nLa commande jekyll serve lance un serveur web local pour prévisualiser votre site à l’adresse http:\/\/localhost:4000. Je vais garder cet onglet ouvert dans mon navigateur.\n\n\n\n\n\n\nLe thème par défaut de Jekyll.\n\nPlongeons dans les fichiers\nRegardons à quoi ressemble l’arborescence de notre site Jekyll en ouvrant le dossier mon-site dans notre éditeur de texte préféré. J'ai utilisé Sublime Text pendant des années mais récemment je suis passé à Atom.\nC’est bien documenté et les paquets de la communauté sont assez mortels - pigments, Emmet et bezier-curve-editor pour n'en citer que quelques-uns.\n\n\n\n\n\n\nLe dossier mon-site ouvert dans Atom.\n\nPour ce guide, je vais utiliser Atom. Vous remarquerez le panneau avec l’arborescence de fichier sur la gauche. Laissez-moi vous la détailler :\nmon-site\/\n|\n|-- _config.yml    # Configuration de votre site\n|-- _drafts\/       # Articles non publiés\n|-- _includes\/     # Composants HTML réutilisables\n|-- _layouts\/      # Modèles\n|-- _posts\/        # Articles (ou entrées de portfolio !)\n|-- _sass\/         # Fichiers Sass\n|-- _site\/         # Votre site généré\n|-- css\/           # fichier CSS principal\n|-- about.md       # Page à propos\n|-- index.html     # index du site\nC’est l’arborescence par défaut. Tout dossier dont le nom commence par un tiret bas _ 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 layouts, par contre il générera le fichier index.html puisqu'il n'y a pas de tiret bas devant.\nVous 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 :\nassets\/        # images du projet\nimages\/        # fichiers SVG, images diverses\njs\/            # fichiers Javascript, les vôtres et ceux des différentes bibliothèques utilisées.\nModifier les paramètres de votre site\nJekyll inclus ce super fichier nommé _config.yml dans le répertoire racine. Vous pouvez définir n'importe quel paramètre global de votre portfolio dedans. Ouvrons-le et personnalisons tout ça !\n\n\n\n\n\n\nRemplissez vos infos !\n\nJ'ai ajouté un paramètre permalink 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 dribbble_username.\nÀ chaque fois que nous allons modifier _config.yml, 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 ctrl-c. Entez à nouveau jekyll serve et jetez un coup d’œil à localhost !\n\n\n\n\n\n\nLes variables globales sont appliquées !\n\nCette flexibilité c'est ce qui rend Jekyll si fun et simple à mettre en œuvre.\nVoyons comment ce concept est aussi valable pour nos articles.\nAjoutons notre premier article !\nÀ quoi sert un portfolio sans démonstration de notre travail ? Écrivons un article à propos de mon side-projet d’application de livraison de nourriture pour chat, Food Right Meow. Dans le dossier _posts, je vais créer un nouveau fichier Markdown en utilisant la convention ANNEE-MOIS-JOUR-titre2. Le fichier de mon article est donc nommé 2016-11-10-livraison-nourriture-chats.markdown.\n\n\n\n\n\n\nYAML front matter.\n\nVous 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.\nYAML front matter\nFront 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\nÉditons notre front matter en haut de l’article entre les triples tirets :\n\n\n\n\n\n\nC’est ici que nous définissons le titre, la date et la catégorie.\n\nJekyll 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 :\n\n\n\n\n\n\nÇa marche !\n\nLiquid\nAlors comment ça marche tout ça ? Ces variables front matter sont référencées dans le HTML et le Markdown grâce à Liquid, un langage de modélisation très facile à prendre en main. Liquid vous permet d’ajouter de la logique, comme des conditions if\/else et des boucles for, d’assigner des chaines de caractères à des variables. Les trucs entre {{ }} ou {% %} c'est pour faire bosser Liquid !\nSi vous ouvrez le fichier post.html dans le dossier _layouts, nous pouvons le voir en action.\n\n\n\n\n\nEn préfixant nos variables avec page, 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 ?\nPlus de détails sur les variables par ici.\nEt si nous ajoutons encore quelques variables à nous dans le front matter pour épicer un peu nos articles :\n\n\n\n\n\nMaintenant que nous disposons de toutes ces super variables, comment pouvons-nous les utiliser ? Modifions notre modèle de mise en page post.html en utilisant les variables page.type et page.intro :\n\n\n\n\n\n\nRegardez ce qui est en violet.\n\nCool ! 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 index.html ?\nEt leur légende aussi peut-être.\n\n\n\n\n\n\nFront matter c'est de la bombe !\n\nHé, 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.\nÉcrire un article pour de vrai\nVous aurez noté l’extension .md ou .markdown 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.\n\n\n\n\n\nLa 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 markdown=1 et le tour est joué ! Le meilleur des deux mondes. Voici un extrait de l’article une fois généré :\n\n\n\n\n\n\nNe voudrions-nous pas vivre de Purring Cat ?\n\nUn autre exemple d’article en .md\nLorsque 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éé une démo qui montre ce que j'ai fait pour parvenir à des articles plus propres tout en gardant les styles désirés !\nCe 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 fichiers sur Github.\nAjoutons un peu de CSS à tout ça\nMaintenant 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.\n\n\n\n\n\nSass\nVous avez probablement remarqué que les projets Jekyll sont livrés avec des fichiers Sass (avec une extension .scss). Bien que vous n'ayez pas besoin de connaître Sass 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é.\nCes guides pour débutant 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. 😉\nVersionnement avec Git\nMaintenant 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 dépôt — le répertoire du projet — à chaque fois que vous faites un commit, 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 branche du projet et fusionner ensuite tout ça dans la branche master, visible de tous les intervenants du projet.\nConfigurer Git\nTéléchargez et installez Git. De retour dans votre terminal, entrez ces commandes (et ajoutez vos propres infos entre les guillemets) :\ngit config --global user.name “Votre Nom”\ngit config --global user.email \"[adresse mail]\"\nSuper, 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 mon-site, je vais donc taper :\ngit init\nFacile ! Notre nouveau dépôt Git est vide, ajoutons-y donc nos fichiers.\ngit add .\nSuper, 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 !\nUtilisez l’option -m suivi d’un message significatif entre guillemets.\ngit commit -m \"Première entrée du portfolio\"\nNotre projet possède officiellement un historique ! Git fonctionne en local, mais GitHub est un service de stockage distant pour pousser 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.\nCréer un dépôt sur GitHub\nMaintenant que nous avons initialisé Git pour notre portfolio, configurons un dépôt distant sur GitHub.\n\nCréez-vous un compte, si vous n'en possédez pas encore.\nCréez un nouveau dépôt.  \n\n\n\n\n\nDans le coin en haut à droite.\n\n\nDonnez un nom et ajoutez une description à votre dépôt. Créez le dépôt !  \n\n\n\n\n\nMaintenant que vous avez créé le dépôt, vous allez voir cette page s'afficher sur GitHub :  \n\n\n\n\nC’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.\ngit remote add origin connecte les deux dépôts pour permettre le déploiement.\ngit push pousse les commits locaux dans votre dépôt distant !\n\nActualisez la page de votre dépôt distant et félicitez-vous ! ON A RÉUSSI.\nMaintenant le monde entier peut admirer notre super portfolio !\n\n\n\n\n\n\nVoici un guide pour débutant très sympa pour des explications plus détaillées.\nGitHub Pages\nÀ partir de là, nous pouvons aller encore plus loin et configurer notre dépôt pour GitHub Pages — 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 !\nC’est un écosystème complet, accessible depuis votre terminal de confiance.\nMes conseils supplémentaires\nParce que ce guide n'est pas déjà assez long.\nTester sur mobile\nLe test sur mobile est inclus ! Autorisons Jekyll à accéder à notre mobile en lançant le serveur de cette manière dans le terminal :\njekyll serve --host 0.0.0.0\nEnsuite, 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 192.168.x.x:4000 dans votre navigateur mobile. Si vous voulez savoir comment ça marche, lisez cet article.\nSystème de grille\n\n\n\n\n\nPour 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 Jeet parce que j'aime sa syntaxe et sa souplesse. Ceci dit, il y en a des tonnes de super chouettes, comme Neat ou Toast.\nRythme vertical\n\n\n\n\n\n\nFrom Typecast.\n\nLe 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 modular-scale sur mon propre portfolio. Apprenez en davantage sur le rythme vertical\nici ou là.\nFin\nEt 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.\nJ'espère que vous avez aimé construire un portfolio pour Food Right Meow avec moi. Créer et redesigner votre portfolio devrait être fun et enrichissant, alors à vos claviers et à vos lignes de commande !\nTéléchargez ou forkez le dépôt de démo\nVoir la démo en action\n\n\n\n\n\n\n\n\n\nInstallez Ruby et Jekyll à l’aide d’Homebrew sous Mac.&#160;&#8617;\n\n\nLe plugin jekyll-atom facilite la création de posts en respectant cette convention.&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<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>\n<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>\n<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>\n<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>\n<figure>\n<picture title=\"Ce type, OK ?\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1HaArgKPM5NEVxucSnAVkpQ.5e522bccaf5b5f05a9924cafa3af98b6.webp\" width=\"620\" height=\"414\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1HaArgKPM5NEVxucSnAVkpQ.5e522bccaf5b5f05a9924cafa3af98b6.avif\" width=\"620\" height=\"414\">\n<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;\">\n</picture>\n<figcaption>Ce type, OK ?</figcaption>\n</figure>\n<h2 id=\"c-est-quoi-jekyll\">C’est quoi Jekyll ?</h2>\n<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>\n<figure>\n<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=\"\">\n<figcaption>Liz, j'ai été là-bas et c'est incroyable.</figcaption>\n</figure>\n<p>Rapide ✔ Propre ✔ Simple ✔ Complet ✔</p>\n<p>OK, tu m’intéresses.</p>\n<h2 id=\"d茅veloppons-un-portfolio聽\">Développons un portfolio !</h2>\n<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>\n<figure>\n<picture title=\"Le terminal\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1-novrtLzHXbmEmpM10a7Qg.b6a661c92d9286126eb076d1da8289b3.webp\" width=\"700\" height=\"300\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1-novrtLzHXbmEmpM10a7Qg.b6a661c92d9286126eb076d1da8289b3.avif\" width=\"700\" height=\"300\">\n<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;\">\n</picture>\n<figcaption>Le terminal</figcaption>\n</figure>\n<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>\n<p>Installons Jekyll<sup id=\"fnref1:1\"><a href=\"#fn:1\" class=\"footnote-ref\">1</a></sup> ! Dans le terminal, j'ai tapé :</p>\n<pre><code class=\"language-sh hljs bash\">gem install jekyll bundler</code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">jekyll new mon-site\n<span class=\"hljs-built_in\">cd</span> mon-site\njekyll serve</code></pre>\n<p>Excellent ! J'ai donc maintenant un dossier sur mon ordinateur nommé <code>mon-site</code>.<br>\nLa 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>\n<figure>\n<picture title=\"Le thème par défaut de Jekyll.\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1BrFV6ggy3qaZsfUQnojCVQ.c102d8424af18341ce8aaf4ee5436bd1.webp\" width=\"700\" height=\"400\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1BrFV6ggy3qaZsfUQnojCVQ.c102d8424af18341ce8aaf4ee5436bd1.avif\" width=\"700\" height=\"400\">\n<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;\">\n</picture>\n<figcaption>Le thème par défaut de Jekyll.</figcaption>\n</figure>\n<h2 id=\"plongeons-dans-les-fichiers\">Plongeons dans les fichiers</h2>\n<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>\nC’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>\n<figure>\n<picture title=\"Le dossier mon-site ouvert dans Atom.\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/118hVdarzmkx6KkSvty5Uaw.4e6674b500efe8a8b180f7b6aa0c40d8.webp\" width=\"700\" height=\"395\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/118hVdarzmkx6KkSvty5Uaw.4e6674b500efe8a8b180f7b6aa0c40d8.avif\" width=\"700\" height=\"395\">\n<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;\">\n</picture>\n<figcaption>Le dossier mon-site ouvert dans Atom.</figcaption>\n</figure>\n<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>\n<pre><code class=\"language-sh hljs bash\">mon-site/\n|\n|-- _config.yml    <span class=\"hljs-comment\"># Configuration de votre site</span>\n|-- _drafts/       <span class=\"hljs-comment\"># Articles non publiés</span>\n|-- _includes/     <span class=\"hljs-comment\"># Composants HTML réutilisables</span>\n|-- _layouts/      <span class=\"hljs-comment\"># Modèles</span>\n|-- _posts/        <span class=\"hljs-comment\"># Articles (ou entrées de portfolio !)</span>\n|-- _sass/         <span class=\"hljs-comment\"># Fichiers Sass</span>\n|-- _site/         <span class=\"hljs-comment\"># Votre site généré</span>\n|-- css/           <span class=\"hljs-comment\"># fichier CSS principal</span>\n|-- about.md       <span class=\"hljs-comment\"># Page à propos</span>\n|-- index.html     <span class=\"hljs-comment\"># index du site</span></code></pre>\n<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>\n<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>\n<pre><code class=\"language-sh hljs bash\">assets/        <span class=\"hljs-comment\"># images du projet</span>\nimages/        <span class=\"hljs-comment\"># fichiers SVG, images diverses</span>\njs/            <span class=\"hljs-comment\"># fichiers Javascript, les vôtres et ceux des différentes bibliothèques utilisées.</span></code></pre>\n<h2 id=\"modifier-les-parametres-de-votre-site\">Modifier les paramètres de votre site</h2>\n<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>\n<figure>\n<picture title=\"Remplissez vos infos !\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1fF_CWur2wd6DS7uQDGX6ew.19bb1bd00704932f41902264307ba1cb.webp\" width=\"700\" height=\"492\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1fF_CWur2wd6DS7uQDGX6ew.19bb1bd00704932f41902264307ba1cb.avif\" width=\"700\" height=\"492\">\n<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;\">\n</picture>\n<figcaption>Remplissez vos infos !</figcaption>\n</figure>\n<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>\n<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>\n<figure>\n<picture title=\"Les variables globales sont appliquées !\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1uDszfYceRXqPFeI7Xm6m0w.353d90e08aba5feed4060a0b6d113788.webp\" width=\"700\" height=\"398\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1uDszfYceRXqPFeI7Xm6m0w.353d90e08aba5feed4060a0b6d113788.avif\" width=\"700\" height=\"398\">\n<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;\">\n</picture>\n<figcaption>Les variables globales sont appliquées !</figcaption>\n</figure>\n<p>Cette flexibilité c'est ce qui rend Jekyll si fun et simple à mettre en œuvre.<br>\nVoyons comment ce concept est aussi valable pour nos articles.</p>\n<h2 id=\"ajoutons-notre-premier-article聽\">Ajoutons notre premier article !</h2>\n<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>\n<figure>\n<picture title=\"YAML front matter.\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kkyEEvzkXYwHlvstT7Tytg.dccf8f264a327cfe165af568dfd8e954.webp\" width=\"672\" height=\"123\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kkyEEvzkXYwHlvstT7Tytg.dccf8f264a327cfe165af568dfd8e954.avif\" width=\"672\" height=\"123\">\n<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;\">\n</picture>\n<figcaption>YAML front matter.</figcaption>\n</figure>\n<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>\n<h2 id=\"yaml-front-matter\">YAML front matter</h2>\n<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>\n<p>Éditons notre front matter en haut de l’article entre les triples tirets :</p>\n<figure>\n<picture title=\"C’est ici que nous définissons le titre, la date et la catégorie.\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1CJkHCXOIOLYrssnP4zgtiw.7f3680c766404e8e605df441fd3e24bd.webp\" width=\"672\" height=\"125\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1CJkHCXOIOLYrssnP4zgtiw.7f3680c766404e8e605df441fd3e24bd.avif\" width=\"672\" height=\"125\">\n<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;\">\n</picture>\n<figcaption>C’est ici que nous définissons le titre, la date et la catégorie.</figcaption>\n</figure>\n<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>\n<figure>\n<picture title=\"Ça marche !\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Ça marche !</figcaption>\n</figure>\n<h2 id=\"liquid\">Liquid</h2>\n<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>\n<p>Si vous ouvrez le fichier <code>post.html</code> dans le dossier <code>_layouts</code>, nous pouvons le voir en action.</p>\n<picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1FYILLFGGLdTohX7sJZjSIw.d19ab2494b214fa07fbd311e52f401d2.webp\" width=\"700\" height=\"403\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1FYILLFGGLdTohX7sJZjSIw.d19ab2494b214fa07fbd311e52f401d2.avif\" width=\"700\" height=\"403\">\n<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;\">\n</picture>\n<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>\n<a href=\"https://jekyllrb.com/docs/variables/\" target=\"_blank\" rel=\"noopener noreferrer\">Plus de détails sur les variables par ici</a>.</p>\n<p>Et si nous ajoutons encore quelques variables à nous dans le front matter pour épicer un peu nos articles :</p>\n<picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1j0EnKALwOPpCRrhBPaM7Nw.427eb232b76fa4d3c06df4121e6b014c.webp\" width=\"666\" height=\"206\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1j0EnKALwOPpCRrhBPaM7Nw.427eb232b76fa4d3c06df4121e6b014c.avif\" width=\"666\" height=\"206\">\n<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;\">\n</picture>\n<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>\n<figure>\n<picture title=\"Regardez ce qui est en violet.\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kndQjfPKY_2GF10ZE2Vobg.f125b881274e606a776be433f03bf58f.webp\" width=\"700\" height=\"325\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kndQjfPKY_2GF10ZE2Vobg.f125b881274e606a776be433f03bf58f.avif\" width=\"700\" height=\"325\">\n<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;\">\n</picture>\n<figcaption>Regardez ce qui est en violet.</figcaption>\n</figure>\n<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>\nEt leur légende aussi peut-être.</p>\n<figure>\n<picture title=\"Front matter c&#039;est de la bombe !\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h4YbEuULViTNp3ixMtpxKg.c292423ad062f8959a89bd62bd5c570c.webp\" width=\"700\" height=\"325\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h4YbEuULViTNp3ixMtpxKg.c292423ad062f8959a89bd62bd5c570c.avif\" width=\"700\" height=\"325\">\n<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;\">\n</picture>\n<figcaption>Front matter c'est de la bombe !</figcaption>\n</figure>\n<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>\n<h2 id=\"ecrire-un-article-pour-de-vrai\">Écrire un article pour de vrai</h2>\n<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>\n<picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h1nmOO9BWVno52HIGqkyyQ.b58995197cdab3580537438ad6a34eee.webp\" width=\"700\" height=\"510\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h1nmOO9BWVno52HIGqkyyQ.b58995197cdab3580537438ad6a34eee.avif\" width=\"700\" height=\"510\">\n<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;\">\n</picture>\n<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>\n<figure>\n<picture title=\"Ne voudrions-nous pas vivre de Purring Cat ?\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1X7jkghcRXQKIH2BSCIxvVQ.a90c9b45829412be3297f1ffebe15be7.webp\" width=\"700\" height=\"548\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1X7jkghcRXQKIH2BSCIxvVQ.a90c9b45829412be3297f1ffebe15be7.avif\" width=\"700\" height=\"548\">\n<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;\">\n</picture>\n<figcaption>Ne voudrions-nous pas vivre de Purring Cat ?</figcaption>\n</figure>\n<h2 id=\"un-autre-exemple-d-article-en-md\">Un autre exemple d’article en .md</h2>\n<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>\nCe 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>\n<h2 id=\"ajoutons-un-peu-de-css-a-tout-ca\">Ajoutons un peu de CSS à tout ça</h2>\n<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>\n<picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nHxZAUcaEIoPxYPB9UwQg.013adf1162ebe62bcb526aa5ed62c655.webp\" width=\"700\" height=\"325\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nHxZAUcaEIoPxYPB9UwQg.013adf1162ebe62bcb526aa5ed62c655.avif\" width=\"700\" height=\"325\">\n<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;\">\n</picture>\n<h2 id=\"sass\">Sass</h2>\n<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>\nCes <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>\n<h2 id=\"versionnement-avec-git\">Versionnement avec Git</h2>\n<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>\n<h2 id=\"configurer-git\">Configurer Git</h2>\n<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>\n<pre><code class=\"language-sh hljs bash\">git config --global user.name “Votre Nom”\ngit config --global user.email <span class=\"hljs-string\">\"[adresse mail]\"</span></code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">git init</code></pre>\n<p>Facile ! Notre nouveau dépôt Git est vide, ajoutons-y donc nos fichiers.</p>\n<pre><code class=\"language-sh hljs bash\">git add .</code></pre>\n<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>\nUtilisez l’option <code>-m</code> suivi d’un message significatif entre guillemets.</p>\n<pre><code class=\"language-sh hljs bash\">git commit -m <span class=\"hljs-string\">\"Première entrée du portfolio\"</span></code></pre>\n<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>\n<h2 id=\"creer-un-depot-sur-github\">Créer un dépôt sur GitHub</h2>\n<p>Maintenant que nous avons initialisé Git pour notre portfolio, configurons un dépôt distant sur GitHub.</p>\n<ol>\n<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>\n<li>Créez un <a href=\"https://github.com/new\" target=\"_blank\" rel=\"noopener noreferrer\">nouveau dépôt</a>.  <figure>\n<picture title=\"Dans le coin en haut à droite.\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Dans le coin en haut à droite.</figcaption>\n</figure>\n</li>\n<li>Donnez un nom et ajoutez une description à votre dépôt. Créez le dépôt !  <picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1_kV4Itznh62DLnpdKBCGSA.5a7532bd736bcbd44e0d47660cf45c29.webp\" width=\"700\" height=\"464\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1_kV4Itznh62DLnpdKBCGSA.5a7532bd736bcbd44e0d47660cf45c29.avif\" width=\"700\" height=\"464\">\n<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;\">\n</picture>\n</li>\n<li>Maintenant que vous avez créé le dépôt, vous allez voir cette page s'afficher sur GitHub :  <picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/173bT5OREQUaVsdTpqfbA-w.d702ff21fe9bbc89eb6d365d906d730b.webp\" width=\"700\" height=\"425\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/173bT5OREQUaVsdTpqfbA-w.d702ff21fe9bbc89eb6d365d906d730b.avif\" width=\"700\" height=\"425\">\n<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;\">\n</picture>\n<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>\n<code>git remote add origin</code> connecte les deux dépôts pour permettre le déploiement.<br>\n<code>git push</code> pousse les commits locaux dans votre dépôt distant !</p>\n</li>\n<li>Actualisez la page de votre dépôt distant et félicitez-vous ! ON A RÉUSSI.<br>\nMaintenant le monde entier peut admirer notre super portfolio !</li>\n</ol>\n<picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/147y0QjcanT-aWr1CscE6sA.38d0c9ce4781f0b592b7cebdcb42088e.webp\" width=\"700\" height=\"542\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/147y0QjcanT-aWr1CscE6sA.38d0c9ce4781f0b592b7cebdcb42088e.avif\" width=\"700\" height=\"542\">\n<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;\">\n</picture>\n<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>\n<h2 id=\"github-pages\">GitHub Pages</h2>\n<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>\n<p>C’est un écosystème complet, accessible depuis votre terminal de confiance.</p>\n<h2 id=\"mes-conseils-supplementaires\">Mes conseils supplémentaires</h2>\n<p>Parce que ce guide n'est pas déjà assez long.</p>\n<h3 id=\"tester-sur-mobile\">Tester sur mobile</h3>\n<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>\n<pre><code class=\"language-sh hljs bash\">jekyll serve --host 0.0.0.0</code></pre>\n<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>\n<h2 id=\"systeme-de-grille\">Système de grille</h2>\n<picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nraqBzaZDQU9de3zH5Klw.f221f0a717ef0cf5d813c9c99931d02c.webp\" width=\"700\" height=\"267\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nraqBzaZDQU9de3zH5Klw.f221f0a717ef0cf5d813c9c99931d02c.avif\" width=\"700\" height=\"267\">\n<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;\">\n</picture>\n<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>\n<h2 id=\"rythme-vertical\">Rythme vertical</h2>\n<figure>\n<picture title=\"From Typecast.\">\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h8vVPQhl-HT5AqIlTRoA8g.7918f25313931d3c3d48ff046f5bf43b.webp\" width=\"450\" height=\"300\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h8vVPQhl-HT5AqIlTRoA8g.7918f25313931d3c3d48ff046f5bf43b.avif\" width=\"450\" height=\"300\">\n<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;\">\n</picture>\n<figcaption>From Typecast.</figcaption>\n</figure>\n<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\n<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>\n<h2 id=\"fin\">Fin</h2>\n<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>\n<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>\n<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>\n<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>\n<picture>\n<source type=\"image/webp\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1qKtPxbGF11Ekq_B9hFRmhg.8803e586215c0aac803d38645d0f3b01.webp\" width=\"120\" height=\"120\">\n<source type=\"image/avif\" srcset=\"/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1qKtPxbGF11Ekq_B9hFRmhg.8803e586215c0aac803d38645d0f3b01.avif\" width=\"120\" height=\"120\">\n<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;\">\n</picture>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n<li id=\"fn:2\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/10/29/creer-un-theme-pour-jekyll/",
      "url": "https://jamstatic.fr/2016/10/29/creer-un-theme-pour-jekyll/",
      "title": "Créer un thème pour Jekyll",
      "summary": "Jekyll permet de publier des thèmes sous forme de gem, afin de faciliter l’installation et les mises à jour.",
      "date_published": "2016-10-29T13:18:59+00:00","content_text": "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 bundler. Voyons ensemble à quoi ressemble le workflow de création de thème pour Jekyll.\nPrérequis\nNous 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 la documentation officielle, vous verrez c'est très accessible, c'est du HTML auquel on va ajouter un peu de Liquid, le langage de templating conçu pour les concepteurs de thèmes Shopify, pour accéder à nos données.\nNous 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 _sass, les modèles de pages dans le dossier _layouts, les composants réutilisables dans _includes. Enfin tous les assets (CSS, JS, images, fonts) sont regroupés dans un dossier assets (et non _assets pour éviter les conflits avec le plugin jekyll-assets).\nOn 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.\nLes thèmes seront téléchargés sous forme de gem, vous devez donc avoir installé bundler avec la commande gem install bundler, ce qui est généralement le cas puisque Jekyll encourage son utilisation pour la gestion des dépendances. Enfin, il vous faut créer un compte sur Rubygems pour publier votre thème, ça ne vous prendra que quelques secondes si vous avez déjà un compte GitHub.\nPréparer son thème\nVous 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 new-theme pour générer une structure de base dans laquelle vous allez pouvoir ajouter vos fichiers :\nbundle exec jekyll new-theme mon-super-theme\nCette 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 ?\nCe dossier comprend tous les dossiers évoqués plus haut : _includes, _layouts, _sass et assets ainsi qu'un fichier Gemfile et un fichier mon-super-theme.gempsec qui contient les informations sur votre thème.\nVous pouvez maintenant recopier les fichiers de votre thème dans cette structure d’exemple.\nL'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 :\n├── .gitignore\n├── Gemfile\n├── LICENSE.md\n├── README.md\n├── _includes\n│   └── head.html\n│   └── …\n├── _layouts\n│   ├── default.html\n│   ├── home.html\n│   ├── page.html\n│   └── post.html\n│   └── …\n├── _sass\n│   ├── _base.scss\n│   ├── _variables.scss\n│   ├── …\n├── assets\n│   ├── favicons\n│   ├── fonts\n│   └── images\n│   └── js\n│   └── css\n├── demo\n│   ├── 404.html\n│   ├── Gemfile\n│   ├── _config.yml\n│   ├── _posts\n│   │   ├── 2016-10-20-presentation-de-jekyll.md\n│   │   ├── 2016-01-02-exemple-de-contenu.md\n│   │   └── 2016-01-03-introduction.md\n│   │   └── …\n├── mon-super-theme.gemspec\n└── screenshot.png\nParmi les différences avec un site Jekyll classique, on remarque la présence d’un fichier Gemfile un peu particulier, d’un fichier gemspec pour la spécification de la gem, d’un fichier LICENSE (MIT par défaut), d’un fichier README.md, d’une capture d’écran et d’un dossier demo (ou example, c'est selon).\nSi on regarde le contenu du fichier Gemfile 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 :\nsource \"https:\/\/rubygems.org\"\ngemspec\nEn effet, c'est le fichier gemspec 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 documentation de référence des spécifications d’une gem.\nLorsque vous utilisez la commande new-theme de Jekyll, par défaut, le fichier de spécification de votre gem ressemble à ça :\n# frozen-string-literal: true\nGem::Specification.new do |spec|\n  spec.name     = \"mon-super-theme\"\n  spec.version  = \"0.1.0\"\n  spec.authors  = [\"Frank Taillandier\"]\n  spec.email    = [\"frank@jekyllrb.com\"]\n  spec.summary  = %q{TODO : Une courte description de votre thème, requise par Rubygems.}\n  spec.homepage = \"TODO : Indiquez ici l’adresse du site de votre gem ou l’URL publique de son dépôt\"\n  spec.license  = \"MIT\"\n  spec.files    = `git ls-files -z`.split(\"\\x0\").select { |f| f.match(%r{^(assets|_layouts|_includes|_sass|LICENSE|README)}i) }\n  spec.add_development_dependency \"jekyll\", \"~&gt; 3.5\"\n  spec.add_development_dependency \"bundler\", \"~&gt; 1.15\"\n  spec.add_development_dependency \"rake\", \"~&gt; 12.0\"\nend\nOutre 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 : git init &amp;&amp; git add . &amp;&amp; git commit -m \"Initial commit\".\nOn 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 assets n'était pas géré et le support initial des thèmes n'est arrivé qu'avec la version 3.2.\nOn constate aussi que bundler est déclaré en tant que dépendance pour le développement, ainsi que rake, souvent utilisé pour ajouter des tests ou des tâches automatisées.\nVous 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.\nNous ajoutons donc les dépendances ainsi que les versions minimales requises, comme nous le ferions dans un fichier Gemfile à l’aide de spec.add_runtime_dependency :\nspec.add_runtime_dependency \"jekyll-feed\", \"~&gt; 0.8\"\nspec.add_runtime_dependency \"jekyll-sitemap\", \"~&gt; 0.12\"\nspec.add_runtime_dependency \"jekyll-seo-tag\", \"~&gt; 2.0\"\nUne fois que votre fichier de spécification est complété, vous allez pouvoir tester votre thème.\nTester son thème\nCommencez par lancer bundle install pour installer les dépendances mentionnées dans le fichier gemspec.\nFournir des exemples de contenu\nComme 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. Lors d’une mise à jour de thème, tous ces fichiers seront écrasés, 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.\nCependant, 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.\nAfin de ne pas mélanger les fichiers d’exemple avec ceux de votre thème, il est donc conseillé de créer un dossier demo (ou example ou ce que vous voulez) et d’y stocker des contenus destinés à présenter aux utilisateurs le rendu de votre thème.\nEn plus des fichiers générés par la commande new-theme, nous ajouterons dans ce dossier demo tout ce qu'il faut pour faire tourner un site sous Jekyll : un fichier _config.yml, un fichier Gemfile ainsi que des pages et des posts bien entendu. Vous pouvez également ajouter des exemples de données dans le dossier _data voire des collections, comme vous le feriez dans n'importe quel site.\nLe fichier demo\/Gemfile ressemble à quelques détails près à celui que vous utiliseriez pour n'importe quel site :\nsource \"https:\/\/rubygems.org\"\ngem \"jekyll\", \"~&gt; 3.3\"\ngem \"mon-super-theme\", path: \"..\/\"\ngroup :jekyll_plugins do\n  gem \"jekyll-sitemap\"\n  gem \"jekyll-seo-tag\"\n  gem \"jekyll-feed\"\nend\nNous 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.\nMaintenant, pour que Jekyll utilise notre thème, nous allons devoir le lui indiquer dans le fichier demo\/_config.yml en ajoutant la ligne :\ntheme: mon-super-theme\nLà encore, le nom utilisé doit être le même que celui de votre fichier gemspec. Nous indiquons également dans ce fichier de configuration que nous utilisons les plugins suivants :\ngems:\n  - jekyll-feed\n  - jekyll-sitemap\n  - jekyll-seo-tag\nNous pouvons maintenant tester notre thème en exécutant la commande bundle exec jekyll serve --source demo depuis le répertoire racine ou cd demo &amp;&amp; bundle exec jekyll serve, c'est du pareil au même. N'oubliez pas d’ajouter le répertoire de destination - _site par défaut - à votre fichier .gitignore.\nVérifiez le rendu sur différents navigateurs et appareils, aidez-vous d’html-proofer si vous souhaitez vous assurer que tous les liens internes fonctionnent :\nbundle exec htmlproofer .\/demo\/_site --disable-external\nVous êtes satisfait de la première version de votre thème ? Parfait ! Veillez maintenant à bien renseigner le fichier README.md pour expliquer comment installer et utiliser votre thème, c'est toujours agréable d’avoir une bonne documentation à sa disposition.\nAfin 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 screenshot.png à la racine de votre dépôt et de l’insérer dans votre fichier README.md.\nPackager son thème\nUne fois que votre thème est fonctionnel, documenté, vous êtes parés pour la publication. Cette étape est déjà documentée sur le site de Jekyll, nous nous contenterons simplement ici de rappeler qu'elle se fait en deux temps.\nLa première commande va créer la gem à proprement parlé à partir du fichier de spécification :\ngem build mon-super-theme.gemspec\n  Successfully built RubyGem\n  Name: mon-super-theme\n  Version: 0.0.1\n  File: mon-super-theme-0.0.1.gem\nComme 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 .gitignore :\n*.gem\nVous pouvez en profiter pour vérifier que l’installation de votre gem se déroule\nsans encombres :\ngem install mon-super-theme-0.0.1.gem\nSuccessfully installed mon-super-theme-0.0.1\n1 gem installed\nSi tout est OK, il ne vous reste maintenant plus qu'à publier votre thème sur Rubygems :\ngem push mon-super-theme-0.0.1.gem\nPushing gem to https:\/\/rubygems.org…\nEt 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 le forum de Jekyll, sur Twitter avec le hashtag #jekyllrb ou en commentaire de ce billet 😄.\nSi vous cherchez des références, vous pouvez toujours prendre exemple sur des structures de thèmes accessibles sur Github, notamment :\n\nMinima, le thème par défaut de Jekyll, idéal pour se familiariser avec la structure que nous venons de voir,\nAlembic un bon point de départ un plus complet proposé par David Darnes\nMinimal mistakes, 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.\n\nVoilà, maintenant c'est à vous de jouer, faites de beaux thèmes pour Jekyll !",
      "content_html": "<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>\n<h2 id=\"prerequis\">Prérequis</h2>\n<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>\n<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>\n<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>\n<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>\n<h2 id=\"preparer-son-theme\">Préparer son thème</h2>\n<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>\n<pre><code class=\"language-sh hljs bash\">bundle <span class=\"hljs-built_in\">exec</span> jekyll new-theme mon-super-theme</code></pre>\n<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>\n<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>\n<p>Vous pouvez maintenant recopier les fichiers de votre thème dans cette structure d’exemple.</p>\n<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>\n<pre><code class=\"language-sh hljs bash\">├── .gitignore\n├── Gemfile\n├── LICENSE.md\n├── README.md\n├── _includes\n│   └── head.html\n│   └── …\n├── _layouts\n│   ├── default.html\n│   ├── home.html\n│   ├── page.html\n│   └── post.html\n│   └── …\n├── _sass\n│   ├── _base.scss\n│   ├── _variables.scss\n│   ├── …\n├── assets\n│   ├── favicons\n│   ├── fonts\n│   └── images\n│   └── js\n│   └── css\n├── demo\n│   ├── 404.html\n│   ├── Gemfile\n│   ├── _config.yml\n│   ├── _posts\n│   │   ├── 2016-10-20-presentation-de-jekyll.md\n│   │   ├── 2016-01-02-exemple-de-contenu.md\n│   │   └── 2016-01-03-introduction.md\n│   │   └── …\n├── mon-super-theme.gemspec\n└── screenshot.png</code></pre>\n<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>\n<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>\n<pre><code class=\"language-ruby hljs ruby\">source <span class=\"hljs-string\">\"https://rubygems.org\"</span>\ngemspec</code></pre>\n<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>\n<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>\n<pre><code class=\"language-ruby hljs ruby\"><span class=\"hljs-comment\"># frozen-string-literal: true</span>\nGem::Specification.new <span class=\"hljs-keyword\">do</span> <span class=\"hljs-params\">|spec|</span>\n  spec.name     = <span class=\"hljs-string\">\"mon-super-theme\"</span>\n  spec.version  = <span class=\"hljs-string\">\"0.1.0\"</span>\n  spec.authors  = [<span class=\"hljs-string\">\"Frank Taillandier\"</span>]\n  spec.email    = [<span class=\"hljs-string\">\"frank@jekyllrb.com\"</span>]\n  spec.summary  = <span class=\"hljs-string\">%q{TODO : Une courte description de votre thème, requise par Rubygems.}</span>\n  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>\n  spec.license  = <span class=\"hljs-string\">\"MIT\"</span>\n  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>) }\n  spec.add_development_dependency <span class=\"hljs-string\">\"jekyll\"</span>, <span class=\"hljs-string\">\"~&gt; 3.5\"</span>\n  spec.add_development_dependency <span class=\"hljs-string\">\"bundler\"</span>, <span class=\"hljs-string\">\"~&gt; 1.15\"</span>\n  spec.add_development_dependency <span class=\"hljs-string\">\"rake\"</span>, <span class=\"hljs-string\">\"~&gt; 12.0\"</span>\n<span class=\"hljs-keyword\">end</span></code></pre>\n<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>\n<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>\n<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>\n<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>\n<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>\n<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>\nspec.add_runtime_dependency <span class=\"hljs-string\">\"jekyll-sitemap\"</span>, <span class=\"hljs-string\">\"~&gt; 0.12\"</span>\nspec.add_runtime_dependency <span class=\"hljs-string\">\"jekyll-seo-tag\"</span>, <span class=\"hljs-string\">\"~&gt; 2.0\"</span></code></pre>\n<p>Une fois que votre fichier de spécification est complété, vous allez pouvoir tester votre thème.</p>\n<h2 id=\"tester-son-theme\">Tester son thème</h2>\n<p>Commencez par lancer <code>bundle install</code> pour installer les dépendances mentionnées dans le fichier <code>gemspec</code>.</p>\n<h3 id=\"fournir-des-exemples-de-contenu\">Fournir des exemples de contenu</h3>\n<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>\n<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>\n<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>\n<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>\n<p>Le fichier <code>demo/Gemfile</code> ressemble à quelques détails près à celui que vous utiliseriez pour n'importe quel site :</p>\n<pre><code class=\"language-ruby hljs ruby\">source <span class=\"hljs-string\">\"https://rubygems.org\"</span>\ngem <span class=\"hljs-string\">\"jekyll\"</span>, <span class=\"hljs-string\">\"~&gt; 3.3\"</span>\ngem <span class=\"hljs-string\">\"mon-super-theme\"</span>, <span class=\"hljs-symbol\">path:</span> <span class=\"hljs-string\">\"../\"</span>\ngroup <span class=\"hljs-symbol\">:jekyll_plugins</span> <span class=\"hljs-keyword\">do</span>\n  gem <span class=\"hljs-string\">\"jekyll-sitemap\"</span>\n  gem <span class=\"hljs-string\">\"jekyll-seo-tag\"</span>\n  gem <span class=\"hljs-string\">\"jekyll-feed\"</span>\n<span class=\"hljs-keyword\">end</span></code></pre>\n<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>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">theme:</span> <span class=\"hljs-string\">mon-super-theme</span></code></pre>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">gems:</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">jekyll-feed</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">jekyll-sitemap</span>\n  <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">jekyll-seo-tag</span></code></pre>\n<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>\n<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>\n<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>\n<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>\n<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>\n<h3 id=\"packager-son-theme\">Packager son thème</h3>\n<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>\n<p>La première commande va créer la gem à proprement parlé à partir du fichier de spécification :</p>\n<pre><code class=\"language-sh hljs bash\">gem build mon-super-theme.gemspec\n  Successfully built RubyGem\n  Name: mon-super-theme\n  Version: 0.0.1\n  File: mon-super-theme-0.0.1.gem</code></pre>\n<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>\n<pre><code class=\"language-ruby hljs ruby\">*.gem</code></pre>\n<p>Vous pouvez en profiter pour vérifier que l’installation de votre gem se déroule\nsans encombres :</p>\n<pre><code class=\"language-sh hljs bash\">gem install mon-super-theme-0.0.1.gem\nSuccessfully installed mon-super-theme-0.0.1\n1 gem installed</code></pre>\n<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>\n<pre><code class=\"language-sh hljs bash\">gem push mon-super-theme-0.0.1.gem\nPushing gem to https://rubygems.org…</code></pre>\n<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>\n<p>Si vous cherchez des références, vous pouvez toujours prendre exemple sur des structures de thèmes accessibles sur Github, notamment :</p>\n<ul>\n<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>\n<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>\n<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>\n</ul>\n<p>Voilà, maintenant c'est à vous de jouer, faites de beaux thèmes pour Jekyll !</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/10/07/jekyll-3-3-est-dispo/",
      "url": "https://jamstatic.fr/2016/10/07/jekyll-3-3-est-dispo/",
      "title": "Les nouveautés de Jekyll 3.3",
      "summary": "Jekyll 3.3 intègre le support des assets dans les thèmes, de nouveaux filtres pour les URLs et bien plus encore.",
      "date_published": "2016-10-07T00:00:00+00:00","content_text": "Plein de nouveautés pour vous simplifier la vie dans la version\n3.3 de Jekyll. On retiendra trois fonctionnalités à tester en priorité.\nLes thèmes peuvent désormais fournir des assets statiques et dynamiques dans le dossier \/assets\nDepuis Jekyll 3.2, il est possible de packager un thème sous forme de\ngem, il était déjà possible d’embarquer des\nincludes, des layouts et des fichiers Sass. Avec la version 3.3, il est enfin\npossible d’ajouter aussi des assets à son thème. Les développeurs de thèmes vont\ndonc pouvoir fournir des thèmes complets et les utilisateurs pourront les tester\net les mettre à jour plus simplement. Cette fonctionnalité n'est pas encore très\nmature et le support de fichier de configuration pour les thèmes ou l’ajout d’un\ndossier data sont en cours de discussion.\nPour faciliter davantage la gestion de thème, tout fichier présent dans le\ndossier \/assets de votre thème sera considéré comme faisant partie du site de\nl’utilisateur du thème. Vous pouvez donc ajouter du Sass, du JavaScript, du\nCoffeeScript, des images, des fontes et tout ce qui sera utile à la présentation\net au comportement de votre thème. Les règles sont les mêmes que dans Jekyll :\nsi un fichier comporte des entêtes YAML Front Matter, il sera converti et traité\npar le moteur de rendu. Si le fichier ne comporte aucun en-tête YFM, il sera\nsimplement copié comme un asset statique.\nNotez bien que les fichiers de l’utilisateur avec le même chemin prennent\ntoujours le pas sur ceux de votre thème. Par exemple si un fichier\n\/assets\/main.scss est présent dans le dossier du site de l’utilisateur, c'est\nlui qui sera traité en lieu et place du fichier \/assets\/main.scss présent dans\nla gem de votre thème.\nNous vous invitons à vous reporter à la\ndocumentation officielle sur la gestion des assets dans les thèmes\npour plus d’informations.\nLes filtres relative_url et absolute_url\nDeux nouveaux filtres font leur apparition pour simplifier la gestion des URLs\ndans vos templates. Fini de vous emmêler les pinceaux avec baseurl et url.\nLorsque vous développez en local, si vous définissez la valeur de baseurl afin\nqu'elle corresponde à votre environnement de développement, mettons par exemple\nbaseurl: \"\/mondossier\", le filtre relative_url se chargera de préfixer cette\nvaleur pour toutes les URLs que vous appellerez :\n{{ \"\/docs\/assets\/\" | relative_url }} =&gt; \/mondossier\/docs\/assets\nPar défaut, baseurl est défini à \"\" et sera remplacé tel quel (ne définissez\njamais cette valeur à \"\/\"):\n{{ \"\/docs\/assets\/\" | relative_url }} =&gt; \/docs\/assets\nLe résultat d’un appel à relative_url produira toujours une URL relative au\ndomaine racine. Le même principe s'applique au filtre absolute_url, il ajoute\nles valeurs définies dans baseurl et url et facilite ainsi la création\nd’URLs absolues :\n{{ \"\/docs\/assets\/\" | absolute_url }} =&gt; http:\/\/jamstatic.fr\/mondossier\/docs\/assets\nsite.url est maintenant défini pour le serveur de développement\nQuand vous lancez la commande jekyll serve en local, elle va démarrer un\nserveur web, généralement à l’adresse http:\/\/localhost:4000, sur lequel vous\nallez pouvoir prévisualiser votre site durant la phase de développement. Si vous\nutilisez le filtre absolute_url ou site.url dans votre code, vous avez\nprobablement dû créer un fichier de configuration pour le développement, qui va\nréinitialiser la valeur d’url à http:\/\/localhost:4000.\nC’est maintenant inutile ! Quand vous lancez la commande jekyll serve, Jekyll\nva générer votre site avec les valeurs de host, port et des options\nrelatives à SSL. Par défaut ce sera donc url: http:\/\/localhost:4000. Quand\nvous développez en local, la valeur de site.url sera donc remplacée par\nhttp:\/\/localhost:4000.\nC’est le comportement par défaut lorsque vous exécutez Jekyll en local. Ce ne\nsera pas le cas si vous exécutez jekyll serve si vous précisez un\nenvironnement de production avec JEKYLL_ENV=production. Si la variable\nd’environnement JEKYLL_ENV possède une autre valeur que development (sa\nvaleur par défaut), Jekyll n'écrasera pas la valeur du paramètre url définie\ndans votre fichier de configuration. Attention, cela ne s'applique qu'à la\ncommande serve, pas à la commande build`.\nEt bien plus encore\nPour avoir le détail de tous les correctifs et les améliorations mineures\napportées, vous pouvez\nconsulter le CHANGELOG complet.\nJekyllez bien !",
      "content_html": "<aside class=\"note note-intro\"><p>Plein de nouveautés pour vous simplifier la vie dans la version\n3.3 de Jekyll. On retiendra trois fonctionnalités à tester en priorité.</p></aside>\n<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>\n<p>Depuis Jekyll 3.2, il est possible de packager un thème sous forme de\n<a href=\"http://guides.rubygems.org/\" target=\"_blank\" rel=\"noopener noreferrer\">gem</a>, il était déjà possible d’embarquer des\nincludes, des layouts et des fichiers Sass. Avec la version 3.3, il est enfin\npossible d’ajouter aussi des assets à son thème. Les développeurs de thèmes vont\ndonc pouvoir fournir des thèmes complets et les utilisateurs pourront les tester\net les mettre à jour plus simplement. Cette fonctionnalité n'est pas encore très\nmature et le support de fichier de configuration pour les thèmes ou l’ajout d’un\ndossier <code>data</code> sont en cours de discussion.</p>\n<p>Pour faciliter davantage la gestion de thème, tout fichier présent dans le\ndossier <code>/assets</code> de votre thème sera considéré comme faisant partie du site de\nl’utilisateur du thème. Vous pouvez donc ajouter du Sass, du JavaScript, du\nCoffeeScript, des images, des fontes et tout ce qui sera utile à la présentation\net au comportement de votre thème. Les règles sont les mêmes que dans Jekyll :\nsi un fichier comporte des entêtes YAML Front Matter, il sera converti et traité\npar le moteur de rendu. Si le fichier ne comporte aucun en-tête YFM, il sera\nsimplement copié comme un asset statique.</p>\n<p>Notez bien que les fichiers de l’utilisateur avec le même chemin prennent\ntoujours le pas sur ceux de votre thème. Par exemple si un fichier\n<code>/assets/main.scss</code> est présent dans le dossier du site de l’utilisateur, c'est\nlui qui sera traité en lieu et place du fichier <code>/assets/main.scss</code> présent dans\nla gem de votre thème.</p>\n<p>Nous vous invitons à vous reporter à la\n<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>\npour plus d’informations.</p>\n<h2 id=\"les-filtres-relative-url-et-absolute-url\">Les filtres <code>relative_url</code> et <code>absolute_url</code></h2>\n<p>Deux nouveaux filtres font leur apparition pour simplifier la gestion des URLs\ndans vos templates. Fini de vous emmêler les pinceaux avec <code>baseurl</code> et <code>url</code>.\nLorsque vous développez en local, si vous définissez la valeur de <code>baseurl</code> afin\nqu'elle corresponde à votre environnement de développement, mettons par exemple\n<code>baseurl: \"/mondossier\"</code>, le filtre <code>relative_url</code> se chargera de préfixer cette\nvaleur pour toutes les URLs que vous appellerez :</p>\n<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>\n<p>Par défaut, <code>baseurl</code> est défini à <code>\"\"</code> et sera remplacé tel quel (ne définissez\njamais cette valeur à <code>\"/\"</code>):</p>\n<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>\n<p>Le résultat d’un appel à <code>relative_url</code> produira toujours une URL relative au\ndomaine racine. Le même principe s'applique au filtre <code>absolute_url</code>, il ajoute\nles valeurs définies dans <code>baseurl</code> et <code>url</code> et facilite ainsi la création\nd’URLs absolues :</p>\n<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>\n<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>\n<p>Quand vous lancez la commande <code>jekyll serve</code> en local, elle va démarrer un\nserveur web, généralement à l’adresse <code>http://localhost:4000</code>, sur lequel vous\nallez pouvoir prévisualiser votre site durant la phase de développement. Si vous\nutilisez le filtre <code>absolute_url</code> ou <code>site.url</code> dans votre code, vous avez\nprobablement dû créer un fichier de configuration pour le développement, qui va\nréinitialiser la valeur d’<code>url</code> à <code>http://localhost:4000</code>.</p>\n<p>C’est maintenant inutile ! Quand vous lancez la commande <code>jekyll serve</code>, Jekyll\nva générer votre site avec les valeurs de <code>host</code>, <code>port</code> et des options\nrelatives à SSL. Par défaut ce sera donc <code>url: http://localhost:4000</code>. Quand\nvous développez en local, la valeur de <code>site.url</code> sera donc remplacée par\n<code>http://localhost:4000</code>.</p>\n<p>C’est le comportement par défaut lorsque vous exécutez Jekyll en local. Ce ne\nsera pas le cas si vous exécutez <code>jekyll serve</code> si vous précisez un\nenvironnement de production avec <code>JEKYLL_ENV=production</code>. Si la variable\nd’environnement <code>JEKYLL_ENV</code> possède une autre valeur que <code>development</code> (sa\nvaleur par défaut), Jekyll n'écrasera pas la valeur du paramètre <code>url</code> définie\ndans votre fichier de configuration. Attention, cela ne s'applique qu'à la\ncommande <code>serve</code>, pas à la commande build`.</p>\n<h2 id=\"et-bien-plus-encore\">Et bien plus encore</h2>\n<p>Pour avoir le détail de tous les correctifs et les améliorations mineures\napportées, vous pouvez\n<a href=\"https://jekyllrb.com/docs/history/#v3-3-0\" target=\"_blank\" rel=\"noopener noreferrer\">consulter le CHANGELOG complet</a>.</p>\n<p>Jekyllez bien !</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/09/18/utiliser-des-plugins-jekyll-avec-github-pages/",
      "url": "https://jamstatic.fr/2016/09/18/utiliser-des-plugins-jekyll-avec-github-pages/",
      "title": "Utiliser des plugins Jekyll sur GitHub Pages",
      "summary": "Automatiser la publication d'un site Jekyll sur GitHub Pages quels que soient les plugins utilisés.",
      "date_published": "2016-09-18T11:51:13+00:00",
      "date_modified": "2010-09-18T11:51:13+00:00","content_text": "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 jekyll-cloudinary ou jekyll-assets, il va falloir utiliser GitHub Actions ou générer votre site localement avant de le pousser en ligne.\nMise à jour : GitHub Actions permet maintenant de publier un site Jekyll quels que soient les plugins utilisés.\nNous allons voir que cette opération est facilement automatisable à l’aide d’un fichier Rakefile, la manière la plus courante en Ruby de créer des tâches.\nPrérequis\nNous 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 documentation officielle.\nComme nous allons utiliser rake pour écrire une tâche automatisée, il vous faut ajoutez la dépendance à votre fichier Gemfile, si elle n'est pas déjà présente :\n  gem \"rake\"\nUne fois que c'est fait, lancez bundle install pour installer rake.\nMaintenant 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.\nPages utilisateur et organisation\nPour 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 username\/username.github.io.\nGitHub va utiliser la branche master de ces dépôts et publier les pages. Cela fait que nous aurons une branche master qui contient le site généré et une branche source avec les sources de notre site.\nConfiguration du dépôt\nLa préparation du dépôt se résume à créer la branche source en ligne de commande :\ngit checkout -b source master\ngit push -u origin source\nMaintenant que vous avez créé la branche source, vous pouvez en faire la branche par défaut dans GitHub :\n\n\n\n\n\n\nParamétrage des branches dans GitHub.\n\nPublication automatique\nMaintenant 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 master. Mais plutôt que de s'embêter à faire ça manuellement, créons un simple tâche rake. Créez (si vous n'en avez pas déjà un) un fichier Rakefile à la racine de votre site et ajoutez le contenu suivant 1 :\n\nMaintenant vous pouvez simplement lancer la commande rake publish pour générer et publier votre site sur GitHub Pages.\nSi 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.\nPages projet\nLes pages projet sont presque pareilles que les pages utilisateur et organisation, à une différence près : la branche gh-pages est utilisée à la place de la branche master pour générer et publier les pages.\nIl n'y a aucune configuration supplémentaire à faire, il faut simplement apporter quelques petites modifications au fichier Rakefile :\n\nVous pouvez maintenant lancer rake site:publish pour générer votre site et le publier sur GitHub. Jetez également un coup d’œil au fichier Rakefile de Jekyll pour une implémentation alternative de la tâche rake site:publish.\n\n\n\n\n\n\nOctoJekyll.\n\nEnfin, sachez qu'il existe d’autres solutions d’hébergement comme GitLab Pages, Netlify, Cloudcannon, Siteleaf ou Forestry.io qui vous permettent d’utiliser les plugins de votre choix, sans avoir recours à ce genre de hack.\n\n\n\n\nLes tâches utilisées dans ce billet ont été écrites par Ixti, le créateur du plugin jekyll-assets.&#160;&#8617;\n\n\n",
      "content_html": "<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>\n<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>\n<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>\n<h2 id=\"prerequis\">Prérequis</h2>\n<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>\n<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>\n<pre><code class=\"language-ruby hljs ruby\">  gem <span class=\"hljs-string\">\"rake\"</span></code></pre>\n<p>Une fois que c'est fait, lancez <code>bundle install</code> pour installer <code>rake</code>.</p>\n<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>\n<h2 id=\"pages-utilisateur-et-organisation\">Pages utilisateur et organisation</h2>\n<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>\n<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>\n<h3 id=\"configuration-du-depot\">Configuration du dépôt</h3>\n<p>La préparation du dépôt se résume à créer la branche <code>source</code> en ligne de commande :</p>\n<pre><code class=\"language-sh hljs bash\">git checkout -b <span class=\"hljs-built_in\">source</span> master\ngit push -u origin <span class=\"hljs-built_in\">source</span></code></pre>\n<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>\n<figure>\n<picture title=\"Paramétrage des branches dans GitHub.\">\n<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\">\n<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\">\n<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;\">\n</picture>\n<figcaption>Paramétrage des branches dans GitHub.</figcaption>\n</figure>\n<h3 id=\"publication-automatique\">Publication automatique</h3>\n<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>\n<p><script src=\"https://gist.github.com/DirtyF/24cb9c96b64173ecd85578f38bcc940d.js\"></p>\n<p>Maintenant vous pouvez simplement lancer la commande <code>rake publish</code> pour générer et publier votre site sur GitHub Pages.</p>\n<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>\n<h2 id=\"pages-projet\">Pages projet</h2>\n<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>\n<p>Il n'y a aucune configuration supplémentaire à faire, il faut simplement apporter quelques petites modifications au fichier <code>Rakefile</code> :</p>\n<p><script src=\"https://gist.github.com/DirtyF/2eacfb7ecec18b3b738af1c3c8d1fe5e.js\"></p>\n<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>\n<figure>\n<picture title=\"OctoJekyll.\">\n<source type=\"image/webp\" srcset=\"/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346531/octojekyll.b00628737e6e4aed46779c1e681a5b39.webp\" width=\"660\" height=\"552\">\n<source type=\"image/avif\" srcset=\"/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346531/octojekyll.b00628737e6e4aed46779c1e681a5b39.avif\" width=\"660\" height=\"552\">\n<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;\">\n</picture>\n<figcaption>OctoJekyll.</figcaption>\n</figure>\n<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>\n<div class=\"footnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<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>\n</li>\n</ol>\n</div>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/08/31/gestion-images-responsive-avec-jekyll-cloudinary/",
      "url": "https://jamstatic.fr/2016/08/31/gestion-images-responsive-avec-jekyll-cloudinary/",
      "title": "Gérer les images responsive dans Jekyll avec le plugin Cloudinary",
      "summary": "Nicolas Hoizey présente les fonctionnalités de gestion d’images responsive offertes par le plugin Cloudinary qu'il a développé pour Jekyll.",
      "date_published": "2016-08-31T00:00:00+00:00","content_text": "J'ai récemment mis à jour mon site perso avec la version 3.0 de Jekyll et j'en ai profité pour changer quelques outils.\nLes 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.\nPour générer le code HTML des images responsive (dois-je vous vraiment vous rappeler qu'utiliser les images responsive natives devrait être un réflexe de nos jours ?), j'ai testé le plugin Jekyll Responsive Image.\nIl est vraiment sympa, il vous laisse définir vos propres gabarits de balisage d’image, vous pouvez donc utiliser srcset ou &lt;picture&gt; selon votre envie.\nMais il ne répondait à tous mes besoins :\n\nLors 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,\nEnvoyer toutes ces variantes au serveur prend également du temps, car je n'ai pas un accès très rapide chez moi,\nEt 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é.\n\nJe voulais revenir à un workflow plus simple et plus rapide et qui génère moins de charge côté serveur.\nLa 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.\nCloudinary est une des solutions disponibles qui offre le plus de fonctionnalités et qui peut être utilisée gratuitement si vous avez des besoins légers. Difficile pour les autres solutions de rivaliser avec cette offre…\nAvec 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.\nLes fonctions principales que je cherchais et que fournit Cloudinary sont :\n\nLa possibilité d’utiliser le service comme un proxy : 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.\nRecadrage et options de redimensionnement des images : 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 fonctionnalités de recadrage automagiques de Cloudinary.\nOptimisation du format des images : 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.\nOptimisation de la compression d’image : 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.\n\nPersuadé que Cloudinary répondait à toutes mes attentes, il me fallait encore développer un plugin Jekyll qui puisse utiliser ces fonctionnalités.\nAprès réflexion, j'ai décidé de partir avec une balise Liquid {% cloudinary %} 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 plugin Jekyll Cloudinary en juillet 2016.\nLa syntaxe est assez intuitive :\n{% cloudinary [preset] path\/to\/img.jpg alt=\"alt text\" caption=\"image caption\" %}\nÀ partir de cette entrée, le plugin génère le HTML de l’image responsive, en utilisant les attributs srcset et sizes pour la balise &lt;img&gt; tag (voir la section “varier la taille et la densité” de ce billet pour comprendre comment fonctionnent ces attributs et ce billet qui explique pourquoi vous devriez les utiliser plutôt que &lt;picture&gt;, la plupart du temps).\nL'attribut srcset et son fallback src contiennent les URLs Cloudinary qui récupèrent les images originales du billet à la volée et les retaillent en plusieurs tailles alternatives.\nPar exemple, comme indiqué dans la documentation, ce code dans un fichier Markdown :\n{% cloudinary logo \/assets\/logos\/cloudinary.png alt=\"Cloudinary logo\" %}\nva générer le code HTML suivant :\n&lt;img\n  src=\"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\"\n  srcset=\"\n    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,\n    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,\n    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\n  \"\n  sizes=\"\n    (min-width: 50rem) 13rem,\n    (min-width: 40rem) 25vw,\n    45vw\"\n  class=\"logo\"\n  alt=\"logo Cloudinary\"\n  width=\"480\"\n  height=\"350\"\n\/&gt;\nVous avez entièrement la main sur le nombre d’images générées, leurs résolutions et les attributs sizes (qui aident le navigateur à décider quelle image télécharger). Cela se fait à partir des options de configuration à votre disposition dans votre fichier _config.yml. Voici l’extrait de mon fichier de configuration où je définis les règles pour les logos :\ncloudinary:\n  cloud_name: …\n  presets:\n    logo:\n      min_width: 80\n      max_width: 400\n      fallback_max_width: 200\n      steps: 3\n      sizes: \"(min-width: 50rem) 13rem, (min-width: 40rem) 25vw, 45vw\"\n      figure: never\n      attributes:\n        class: logo\n\ncloud_name: … votre ID personnel Cloudinary\npresets: englobe la liste des préréglages que vous définissez pour vote site\nlogo: est le nom d’un des préréglages, que j'utilise dans le tag Liquid avant le nom du fichier image\nmin_width: 80 définit la largeur minimum d’image générée\nmax_width: 400 définit la largeur maximale d’image générée\nfallback_max_width: 200 définit la largeur de l’image de la solution de repli (src)\nsteps: 3 définit le nombre d’images à générer\nsizes: '(min-width: 50rem) 13rem, (min-width: 40rem) 25vw, 45vw' définit l’attribut sizes de l’image responsive, qui dépend du design et des breakpoints\nfigure: never empêche la génération d’un bloc &lt;figure&gt;\/&lt;img&gt;\/&lt;figcaption&gt; (Je n'en veux généralement pas sur les logos)\nattributes: englobe la liste d’attributs à toujours ajouter aux éléments &lt;figure&gt; et\/ou &lt;img&gt;\nclass: logo ajoute l’attribut class ayant pour valeur logo, 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.\n\nVous pouvez définir toutes ces règles pour autant de préréglages dont vous aurez\nbesoin.\nAvec ce plugin et mon compte Cloudinary, 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% et je n'ai plus à me soucier du tout de l’optimisation de mes images. C’est un gain énorme.\nEt après ? Au début, je voulais permettre aux auteurs d’utiliser simplement la syntaxe Markdown pour les images, mais je n'y suis pas encore parvenu, malgré quelques réponses valables à mes questions du principal mainteneur de Jekyll Parker Moore lui-même. Il faudra que je creuse les hooks Jekyll à l’avenir.\nAu 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.\nBien entendu, toute aide est la bienvenue pour aider à améliorer le plugin. Il y a déjà quelques anomalies et demandes de fonctionnalités ouvertes. N'hésitez pas à me signaler tout problème ou à partager vos idées, voire à contribuer via des pull requests !",
      "content_html": "<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>\n<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>\n<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>\nIl 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>\nMais il ne répondait à tous mes besoins :</p>\n<ul>\n<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>\n<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>\n<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>\n</ul>\n<p>Je voulais revenir à un workflow plus simple et plus rapide et qui génère moins de charge côté serveur.</p>\n<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>\n<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>\n<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>\n<p>Les fonctions principales que je cherchais et que fournit Cloudinary sont :</p>\n<ul>\n<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>\n<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>\n<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>\n<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>\n</ul>\n<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>\n<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>\n<p>La syntaxe est assez intuitive :</p>\n<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>\n<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>\nL'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>\n<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>\n<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>\n<p>va générer le code HTML suivant :</p>\n<pre><code class=\"language-html hljs xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img</span>\n  <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>\n  <span class=\"hljs-attr\">srcset</span>=<span class=\"hljs-string\">\"\n    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,\n    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,\n    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\n  \"</span>\n  <span class=\"hljs-attr\">sizes</span>=<span class=\"hljs-string\">\"\n    (min-width: 50rem) 13rem,\n    (min-width: 40rem) 25vw,\n    45vw\"</span>\n  <span class=\"hljs-attr\">class</span>=<span class=\"hljs-string\">\"logo\"</span>\n  <span class=\"hljs-attr\">alt</span>=<span class=\"hljs-string\">\"logo Cloudinary\"</span>\n  <span class=\"hljs-attr\">width</span>=<span class=\"hljs-string\">\"480\"</span>\n  <span class=\"hljs-attr\">height</span>=<span class=\"hljs-string\">\"350\"</span>\n/&gt;</span></code></pre>\n<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>\n<pre><code class=\"language-yaml hljs yaml\"><span class=\"hljs-attr\">cloudinary:</span>\n  <span class=\"hljs-attr\">cloud_name:</span> <span class=\"hljs-string\">…</span>\n  <span class=\"hljs-attr\">presets:</span>\n    <span class=\"hljs-attr\">logo:</span>\n      <span class=\"hljs-attr\">min_width:</span> <span class=\"hljs-number\">80</span>\n      <span class=\"hljs-attr\">max_width:</span> <span class=\"hljs-number\">400</span>\n      <span class=\"hljs-attr\">fallback_max_width:</span> <span class=\"hljs-number\">200</span>\n      <span class=\"hljs-attr\">steps:</span> <span class=\"hljs-number\">3</span>\n      <span class=\"hljs-attr\">sizes:</span> <span class=\"hljs-string\">\"(min-width: 50rem) 13rem, (min-width: 40rem) 25vw, 45vw\"</span>\n      <span class=\"hljs-attr\">figure:</span> <span class=\"hljs-string\">never</span>\n      <span class=\"hljs-attr\">attributes:</span>\n        <span class=\"hljs-attr\">class:</span> <span class=\"hljs-string\">logo</span></code></pre>\n<ul>\n<li><code>cloud_name: …</code> votre ID personnel Cloudinary</li>\n<li><code>presets:</code> englobe la liste des préréglages que vous définissez pour vote site</li>\n<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>\n<li><code>min_width: 80</code> définit la largeur minimum d’image générée</li>\n<li><code>max_width: 400</code> définit la largeur maximale d’image générée</li>\n<li><code>fallback_max_width: 200</code> définit la largeur de l’image de la solution de repli (<code>src</code>)</li>\n<li><code>steps: 3</code> définit le nombre d’images à générer</li>\n<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>\n<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>\n<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>\n<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>\n</ul>\n<p>Vous pouvez définir toutes ces règles pour autant de préréglages dont vous aurez\nbesoin.</p>\n<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>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "nhoizey"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/04/19/entretien-avec-parker-moore/",
      "url": "https://jamstatic.fr/2016/04/19/entretien-avec-parker-moore/",
      "title": "Entretien avec Parker Moore de Jekyll",
      "summary": "Traduction française de l’entretien publié le 11 mars 2016 sur Netlify.",
      "date_published": "2016-04-19T00:00:00+00:00","content_text": "\n\n\n\n\n\nParker Moore.\n\nAlors que nous constatons une augmentation constante du nombre d’outils\npermettant de générer des sites statiques à destination des développeurs,\nprofessionnels ou amateurs, aucun d’entre eux n'attire autant l’attention que\nJekyll.\nSa popularité n'est pas vraiment surprenante quand on sait que Jekyll a été créé\npar un des cofondateurs de GitHub et que c'est le moteur qui fait tourner GitHub\npages.\nIl y a quelque temps, nous avons pu assister au lancement de Jekyll 3.0, avec\nla\npublication en direct du commit final\npendant une présentation de Parker Moore, actuellement en charge de la\nmaintenance de Jekyll, lors du Meetup Static Web à San Francisco. Après sa\nprésentation, Moore a pris quelques minutes pour parler avec nous de Jekyll, de\nson développement et du futur des sites web statiques.\nQu'est-ce que vous faites quand vous ne travaillez pas sur Jekyll ?\nJe suis ingénieur logiciel chez Github, entreprise dans laquelle j'aide au\ndéveloppement du service Github Pages. Mon travail consiste aussi à maintenir\nJekyll et à faire grandir la communauté autour du projet.\nPeux-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 ?\nLe parcours était intéressant. J'ai été amené à utiliser Jekyll pour un job\nd’été à l’université de Cornell. Il s'agissait de la refonte du site\ncals.cornell.edu, nous voulions réaliser un nouveau site, et ce de manière\nrapide. Nous voulions garder l’existant et le migrer sur un nouveau système.\nNous avions de nouvelles maquettes et de nouveaux contenus. J'avais déjà entendu\nparler de Jekyll ; je l’avais déjà utilisé. J'ai réussi à convaincre mon\nsupérieur, je ne sais pas comment j'ai fait, mais il a accepté. Pour moi, Jekyll\ncorrespondait exactement à la demande, je devais juste servir du contenu. Au\nfinal, nous avons même utilisé Jekyll pour servir des pages PHP avec un serveur\nLAMP qui permet d’envoyer des mails ou de générer des PDF. J’ai donc utilisé un\npeu Jekyll à cette période et ça m'a beaucoup plu.\nJ'ai regardé les bugs majeurs, il y en avait tellement : plus de 800 sur le\ndépôt, ce qui est énorme. Beaucoup trop pour un projet Open Source majeur, selon\nmoi. J'ai perçu un besoin, j'ai pensé que j'avais suffisamment de temps libre -\nmême si ce n'était pas le cas - pour aider la communauté sur ces corrections.\nPeu avant, j'étais parti travailler en Allemagne pour une société du nom de 6\nWunderkinder – l’éditeur de Wunderlist — j'ai bombardé Tom — NdT: Prester-Werner\nle créateur de Jekyll — de mails, sans cesse. Au final, j'ai publié une lettre\nouverte sur mon blog, ce qui avec le recul était assez fou et immature, mais qui\na eu les effets escomptés. Peu de temps après, Tom m'a répondu : \"Il faut qu'on\nparle, tu as l’air vraiment intéressé, qu'en dis-tu ?\"\nJe venais juste d’avoir un échange avec Brandon Mathis, qui travaille sur\nOctopress, et il m'a ajouté comme contributeur au projet. Et ainsi j'ai pu dire\nà Tom : \"regarde, je suis aussi contributeur d’Octopress, je connais les sites\nstatiques\". Brandon a dû approuver ma candidature j'imagine. Quelques semaines\nplus tard alors que j'étais en visite chez ma sœur à San Francisco, j'ai\nrencontré Tom au siège de GitHub pour parler de Jekyll pendant une heure. Il m'a\ndonné les accès, j'ai corrigé quelques bugs et j'ai commencé à travailler sur\nJekyll 1.\nTu as débuté avec la 0.12.1 ?\nEffectivement, je l’utilisais, et il y avait tant de choses qui ne\nfonctionnaient pas. Je savais que je pouvais corriger tout ça ; je connaissais\nRuby, j'avais fait un peu de Ruby on Rails avant, et j'avais beaucoup appris sur\nle développement web à cette occasion.\nQuelle fut ta première expérience de programmation ?\nÀ la Rochester Institute of Technology, je m'étais rendu avec des camarades\nd’écoles à des journées de codes pour élèves de collège. J'avais 13 ans. Il y a\neu un cours de 30 minutes sur HTML. J'ai adoré. Je n'arrêtais pas d’en écrire,\nencore et encore. Je changeais la couleur de fond avec un attribut… je faisais\ntous ces trucs bêtes et ordinaires.\nQuelle fut ta première rencontre avec les générateurs de site statique modernes ?\nJe ne me souviens pas plus loin que de mon expérience avec Jekyll à Cornell. Je\nsavais que les générateurs de site statique marcheraient, car j'avais codé du\nHTML, du CSS et du JavaScript et je sais ce qu'ils font. J'ai donc décidé de\npartir de ça, j'ai regardé plusieurs outils et Jekyll était celui qui avait le\nplus d’étoiles sur Github et avait l’air d’être le plus populaire. Les gens\nécrivaient dessus et l’utilisaient. GitHub Pages existait déjà. Du coup j'ai\nemprunté cette direction.\nC'était donc une question de masse critique ? Il y avait pas mal de gens, donc tu savais que c'était vivant et actif\nTu sors dans les endroits branchés en général non ? Je me suis dit qu'il y\naurait des contributeurs. Un projet open source a besoin de gens plus que de\ntout autre chose, parce qu'il y a besoin de code.\nLa pire chose à faire sur un forum est de poser une question et de retourner\nvoir tous les jours s'il y a une réponse, mais toujours rien.\nPersonne n'a répondu !\nQue souhaites-tu aux générateurs de site statique ?\nQu'ils soient mieux compris. Je les comprends et tu les comprends, mais je rêve\nd’un monde où ce serait le cas pour tout le monde… et j'imagine que cela\narrivera, car l’informatique s'apprend de plus en plus tôt, la création de sites\nweb est un bon moyen pour commencer à apprendre le code. Mon plus grand souhait\nc'est qu'ils soient mieux compris de tous et perçus comme une vraie solution,\nhonnête et prête pour la production, pour les sociétés intéressées dans la\nréalisation de sites web.\nComment est-il possible de faire cela ?\nUn truc dont j'ai parlé avec Christian et Matt, les fondateurs de Netlify (NdT :\nService d’hébergement et de déploiement de sites statiques) ; c'est que les\nsites statiques, les générateurs de sites statiques, les générateurs statiques,\nle site statique, tout ça a l’air plutôt ennuyeux, ça sonne très technique…\nLe dynamique a l’air bien plus intéressant !\nC’est ça ! Drupal et WordPress sont basés sur le principe de \"l’installation en\n1 clic\". Sur wordpress.com, il est possible d’avoir un site gratuitement avec un\nthème en place. C’est toutes ces petites barrières qui ne sont pas grand-chose\nmais qui font la différence. Donc que je pense que l’accessibilité c'est\nimportant. Des entreprises comme Netlify ou CloudCannon y travaillent. Plusieurs\nsociétés font en sorte qu'il soit plus simple d’apprendre à construire des sites\nstatiques. Je pense qu'il faut faire évoluer le dialogue et notre vocabulaire,\npour ne pas intimider ceux qui sont en train d’apprendre ce que sont les\ngénérateurs de sites statiques. Qu'est-ce que ça veut dire un site statique ?\nC’est un terme qui parle de lui-même site statique. Mais vous avez à apprendre\nla différence entre le statique et le dynamique… qui n'est pas un terme que les\ngens emploient tous les jours. Quelle est la dernière fois où vous avez utilisé\nle mot statique en dehors du cadre professionnel ?\nOui statique ça fait un peu péjoratif dans ce contexte\nOui, il faut véhiculer une vision claire, accessible et s'outiller d’un meilleur\nlexique. Quelque chose qui explique tout ça très bien, sans utiliser de mots qui\nfont peur.\nEn 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\nSi j'avais une tribune, je dirais \"N'abandonnez pas\". Prenez-vous la tête dessus\nun petit moment. Si cela ne marche pas au bout d’une heure, d’un jour, allez\ndemander de l’aide. Je dirai que ne pas abandonner et se prendre la tête sur\nquelque chose est la meilleure façon d’apprendre, d’affronter les problèmes. De\nmanière générale, ma génération n'est vraiment pas douée quand il s'agit de\npersévérer… que ce soit travailler quelque part plus d’un an ou affronter une\ngrande difficulté pendant plus de 15 minutes. C’est comme si tout était éphémère\nde nos jours, contrairement à ce que j'ai pu apprendre à travers les livres\nd’histoires ou les histoires que mes parents et mes grand-parents m'ont raconté.\nNe pas abandonner, c'est avoir confiance dans la technologie du web statique,\nc'est avoir confiance dans les personnes qui développent tout ça, qui font des\nsites web et qui apprennent de ça, c'est avoir confiance dans notre futur.\nEt lorsqu'on n'abandonne pas, on a plus de chance de faire partie de la solution, car on s'est investi dedans.\nOui, c'est exactement ça.",
      "content_html": "<figure>\n<picture title=\"Parker Moore.\">\n<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\">\n<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\">\n<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\">\n</picture>\n<figcaption>Parker Moore.</figcaption>\n</figure>\n<p>Alors que nous constatons une augmentation constante du nombre d’outils\npermettant de générer des sites statiques à destination des développeurs,\nprofessionnels ou amateurs, aucun d’entre eux n'attire autant l’attention que\n<a href=\"https://jekyllrb.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Jekyll</a>.</p>\n<p>Sa popularité n'est pas vraiment surprenante quand on sait que Jekyll a été créé\npar un des cofondateurs de GitHub et que c'est le moteur qui fait tourner GitHub\npages.</p>\n<p>Il y a quelque temps, nous avons pu assister au lancement de Jekyll 3.0, avec\nla\n<a href=\"https://youtu.be/sPZK8w55cBQ?t=37m58s\" target=\"_blank\" rel=\"noopener noreferrer\">publication en direct du commit final</a>\npendant une présentation de Parker Moore, actuellement en charge de la\nmaintenance de Jekyll, lors du Meetup Static Web à San Francisco. Après sa\nprésentation, Moore a pris quelques minutes pour parler avec nous de Jekyll, de\nson développement et du futur des sites web statiques.</p>\n<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>\n<p>Je suis ingénieur logiciel chez Github, entreprise dans laquelle j'aide au\ndéveloppement du service Github Pages. Mon travail consiste aussi à maintenir\nJekyll et à faire grandir la communauté autour du projet.</p>\n<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>\n<p>Le parcours était intéressant. J'ai été amené à utiliser Jekyll pour un job\nd’été à l’université de Cornell. Il s'agissait de la refonte du site\ncals.cornell.edu, nous voulions réaliser un nouveau site, et ce de manière\nrapide. Nous voulions garder l’existant et le migrer sur un nouveau système.\nNous avions de nouvelles maquettes et de nouveaux contenus. J'avais déjà entendu\nparler de Jekyll ; je l’avais déjà utilisé. J'ai réussi à convaincre mon\nsupérieur, je ne sais pas comment j'ai fait, mais il a accepté. Pour moi, Jekyll\ncorrespondait exactement à la demande, je devais juste servir du contenu. Au\nfinal, nous avons même utilisé Jekyll pour servir des pages PHP avec un serveur\nLAMP qui permet d’envoyer des mails ou de générer des PDF. J’ai donc utilisé un\npeu Jekyll à cette période et ça m'a beaucoup plu.</p>\n<p>J'ai regardé les bugs majeurs, il y en avait tellement : plus de 800 sur le\ndépôt, ce qui est énorme. Beaucoup trop pour un projet Open Source majeur, selon\nmoi. J'ai perçu un besoin, j'ai pensé que j'avais suffisamment de temps libre -\nmême si ce n'était pas le cas - pour aider la communauté sur ces corrections.\nPeu avant, j'étais parti travailler en Allemagne pour une société du nom de 6\nWunderkinder – l’éditeur de Wunderlist — j'ai bombardé Tom — NdT: Prester-Werner\nle créateur de Jekyll — de mails, sans cesse. Au final, j'ai publié une lettre\nouverte sur mon blog, ce qui avec le recul était assez fou et immature, mais qui\na eu les effets escomptés. Peu de temps après, Tom m'a répondu : \"Il faut qu'on\nparle, tu as l’air vraiment intéressé, qu'en dis-tu ?\"</p>\n<p>Je venais juste d’avoir un échange avec Brandon Mathis, qui travaille sur\nOctopress, et il m'a ajouté comme contributeur au projet. Et ainsi j'ai pu dire\nà Tom : \"regarde, je suis aussi contributeur d’Octopress, je connais les sites\nstatiques\". Brandon a dû approuver ma candidature j'imagine. Quelques semaines\nplus tard alors que j'étais en visite chez ma sœur à San Francisco, j'ai\nrencontré Tom au siège de GitHub pour parler de Jekyll pendant une heure. Il m'a\ndonné les accès, j'ai corrigé quelques bugs et j'ai commencé à travailler sur\nJekyll 1.</p>\n<h2 id=\"tu-as-debute-avec-la-0-12-1\">Tu as débuté avec la 0.12.1 ?</h2>\n<p>Effectivement, je l’utilisais, et il y avait tant de choses qui ne\nfonctionnaient pas. Je savais que je pouvais corriger tout ça ; je connaissais\nRuby, j'avais fait un peu de Ruby on Rails avant, et j'avais beaucoup appris sur\nle développement web à cette occasion.</p>\n<h2 id=\"quelle-fut-ta-premiere-experience-de-programmation\">Quelle fut ta première expérience de programmation ?</h2>\n<p>À la Rochester Institute of Technology, je m'étais rendu avec des camarades\nd’écoles à des journées de codes pour élèves de collège. J'avais 13 ans. Il y a\neu un cours de 30 minutes sur HTML. J'ai adoré. Je n'arrêtais pas d’en écrire,\nencore et encore. Je changeais la couleur de fond avec un attribut… je faisais\ntous ces trucs bêtes et ordinaires.</p>\n<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>\n<p>Je ne me souviens pas plus loin que de mon expérience avec Jekyll à Cornell. Je\nsavais que les générateurs de site statique marcheraient, car j'avais codé du\nHTML, du CSS et du JavaScript et je sais ce qu'ils font. J'ai donc décidé de\npartir de ça, j'ai regardé plusieurs outils et Jekyll était celui qui avait le\nplus d’étoiles sur Github et avait l’air d’être le plus populaire. Les gens\nécrivaient dessus et l’utilisaient. GitHub Pages existait déjà. Du coup j'ai\nemprunté cette direction.</p>\n<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>\n<p>Tu sors dans les endroits branchés en général non ? Je me suis dit qu'il y\naurait des contributeurs. Un projet open source a besoin de gens plus que de\ntout autre chose, parce qu'il y a besoin de code.</p>\n<p>La pire chose à faire sur un forum est de poser une question et de retourner\nvoir tous les jours s'il y a une réponse, mais toujours rien.</p>\n<p>Personne n'a répondu !</p>\n<h2 id=\"que-souhaites-tu-aux-generateurs-de-site-statique\">Que souhaites-tu aux générateurs de site statique ?</h2>\n<p>Qu'ils soient mieux compris. Je les comprends et tu les comprends, mais je rêve\nd’un monde où ce serait le cas pour tout le monde… et j'imagine que cela\narrivera, car l’informatique s'apprend de plus en plus tôt, la création de sites\nweb est un bon moyen pour commencer à apprendre le code. Mon plus grand souhait\nc'est qu'ils soient mieux compris de tous et perçus comme une vraie solution,\nhonnête et prête pour la production, pour les sociétés intéressées dans la\nréalisation de sites web.</p>\n<h2 id=\"comment-est-il-possible-de-faire-cela\">Comment est-il possible de faire cela ?</h2>\n<p>Un truc dont j'ai parlé avec Christian et Matt, les fondateurs de Netlify (NdT :\nService d’hébergement et de déploiement de sites statiques) ; c'est que les\nsites statiques, les générateurs de sites statiques, les générateurs statiques,\nle site statique, tout ça a l’air plutôt ennuyeux, ça sonne très technique…</p>\n<h2 id=\"le-dynamique-a-l-air-bien-plus-interessant\">Le dynamique a l’air bien plus intéressant !</h2>\n<p>C’est ça ! Drupal et WordPress sont basés sur le principe de \"l’installation en\n1 clic\". Sur wordpress.com, il est possible d’avoir un site gratuitement avec un\nthème en place. C’est toutes ces petites barrières qui ne sont pas grand-chose\nmais qui font la différence. Donc que je pense que l’accessibilité c'est\nimportant. Des entreprises comme Netlify ou CloudCannon y travaillent. Plusieurs\nsociétés font en sorte qu'il soit plus simple d’apprendre à construire des sites\nstatiques. Je pense qu'il faut faire évoluer le dialogue et notre vocabulaire,\npour ne pas intimider ceux qui sont en train d’apprendre ce que sont les\ngénérateurs de sites statiques. Qu'est-ce que ça veut dire un site statique ?\nC’est un terme qui parle de lui-même site statique. Mais vous avez à apprendre\nla différence entre le statique et le dynamique… qui n'est pas un terme que les\ngens emploient tous les jours. Quelle est la dernière fois où vous avez utilisé\nle mot statique en dehors du cadre professionnel ?</p>\n<h2 id=\"oui-statique-ca-fait-un-peu-pejoratif-dans-ce-contexte\">Oui statique ça fait un peu péjoratif dans ce contexte</h2>\n<p>Oui, il faut véhiculer une vision claire, accessible et s'outiller d’un meilleur\nlexique. Quelque chose qui explique tout ça très bien, sans utiliser de mots qui\nfont peur.</p>\n<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>\n<p>Si j'avais une tribune, je dirais \"N'abandonnez pas\". Prenez-vous la tête dessus\nun petit moment. Si cela ne marche pas au bout d’une heure, d’un jour, allez\ndemander de l’aide. Je dirai que ne pas abandonner et se prendre la tête sur\nquelque chose est la meilleure façon d’apprendre, d’affronter les problèmes. De\nmanière générale, ma génération n'est vraiment pas douée quand il s'agit de\npersévérer… que ce soit travailler quelque part plus d’un an ou affronter une\ngrande difficulté pendant plus de 15 minutes. C’est comme si tout était éphémère\nde nos jours, contrairement à ce que j'ai pu apprendre à travers les livres\nd’histoires ou les histoires que mes parents et mes grand-parents m'ont raconté.\nNe pas abandonner, c'est avoir confiance dans la technologie du web statique,\nc'est avoir confiance dans les personnes qui développent tout ça, qui font des\nsites web et qui apprennent de ça, c'est avoir confiance dans notre futur.</p>\n<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>\n<p>Oui, c'est exactement ça.</p>",
      "authors": [
        {
          "name": "bertrand"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/04/14/jekyllconf-2016/",
      "url": "https://jamstatic.fr/2016/04/14/jekyllconf-2016/",
      "title": "JekyllConf 2016",
      "summary": "Le programme de la deuxième conférence consacrée à Jekyll, qui aura lieu le samedi 7 mai 2016.",
      "date_published": "2016-04-14T00:00:00+00:00","content_text": "JekyllConf est la première conférence\nentièrement consacrée à Jekyll. C’est une conférence en ligne, gratuite et\nouverte à tous, preuve de la volonté de partage qui anime la communauté. Elle\naura lieu le samedi 7 mai 2016 à partir de 19h heure française, les vidéos\nseront mises en ligne peu après.\nLa première édition avait rassemblé Tom Preston-Werner, Parker Moore, Brandon\nMathis ou Ben Balter, respectivement créateurs et mainteneurs de Jekyll,\nOctoPress et GitHub Pages ; outre une vision des futures améliorations possibles\npour les générateurs de sites statiques, il y avait eu quelques retours\nd’expérience plus pratiques comme la création de styleguide. Nous ne pouvons\nque vous inviter à aller regarder les\nvidéos de l’édition de 2015 si vous vous\nintéressez à Jekyll et aux générateurs de site statique.\nLe programme de cette deuxième édition s’annonce tout\naussi intéressant et met encore plus l’accent sur des retours d’expérience ainsi\nque différents cas d’utilisation avancée de Jekyll.\nCette édition permettra de constater comment Jekyll peut répondre à différentes\nproblématiques, que ce soit générer de la documentation, tester ses CSS,\nprototyper ses sites web, travailler sa stratégie de contenu voire faire des\nchoses beaucoup moins conventionnelles comme c’est le cas chez Mapbox.\nLes témoignages de Stack Overflow et quelques-unes des meilleures agences seront\nsurement riches en enseignements. CloudCannon nous montrera comment les\nutilisateurs finaux peuvent éditer des sites sous Jekyll.\nLa conférence compte aussi plusieurs interventions sur des aspects plus\ntechniques comme les différentes manières de déployer Jekyll : sur Amazon,\ndéploiement continu avec Docker… d’implémenter une solution de recherche comme\nAlgolia ou Elasticsearch ou comment gérer les images responsive.\nMise à jour : Toutes les conférences sont disponibles sur\nhttps:\/\/jekyllrb.com\/community\/.",
      "content_html": "<aside class=\"note note-intro\"><p><a href=\"https://jekyllconf.com/\" target=\"_blank\" rel=\"noopener noreferrer\">JekyllConf</a> est la première conférence\nentièrement consacrée à Jekyll. C’est une conférence en ligne, gratuite et\nouverte à tous, preuve de la volonté de partage qui anime la communauté. Elle\naura lieu le samedi 7 mai 2016 à partir de 19h heure française, les vidéos\nseront mises en ligne peu après.</p></aside>\n<p>La première édition avait rassemblé Tom Preston-Werner, Parker Moore, Brandon\nMathis ou Ben Balter, respectivement créateurs et mainteneurs de Jekyll,\nOctoPress et GitHub Pages ; outre une vision des futures améliorations possibles\npour les générateurs de sites statiques, il y avait eu quelques retours\nd’expérience plus pratiques comme la création de <em>styleguide</em>. Nous ne pouvons\nque vous inviter à aller regarder les\n<a href=\"https://jekyllconf.com/2015/\" target=\"_blank\" rel=\"noopener noreferrer\">vidéos de l’édition de 2015</a> si vous vous\nintéressez à Jekyll et aux générateurs de site statique.</p>\n<p>Le <a href=\"https://jekyllconf.com/\" target=\"_blank\" rel=\"noopener noreferrer\">programme</a> de cette deuxième édition s’annonce tout\naussi intéressant et met encore plus l’accent sur des retours d’expérience ainsi\nque différents cas d’utilisation avancée de Jekyll.</p>\n<p>Cette édition permettra de constater comment Jekyll peut répondre à différentes\nproblématiques, que ce soit générer de la documentation, tester ses CSS,\nprototyper ses sites web, travailler sa stratégie de contenu voire faire des\nchoses beaucoup moins conventionnelles comme c’est le cas chez Mapbox.</p>\n<p>Les témoignages de Stack Overflow et quelques-unes des meilleures agences seront\nsurement riches en enseignements. CloudCannon nous montrera comment les\nutilisateurs finaux peuvent éditer des sites sous Jekyll.</p>\n<p>La conférence compte aussi plusieurs interventions sur des aspects plus\ntechniques comme les différentes manières de déployer Jekyll : sur Amazon,\ndéploiement continu avec Docker… d’implémenter une solution de recherche comme\nAlgolia ou Elasticsearch ou comment gérer les images responsive.</p>\n<p><strong>Mise à jour</strong> : Toutes les conférences sont disponibles sur\n<a href=\"https://jekyllrb.com/community/\" target=\"_blank\" rel=\"noopener noreferrer\">https://jekyllrb.com/community/</a>.</p>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    },
    {
      "id": "https://jamstatic.fr/2016/04/01/jamstatic-fr/",
      "url": "https://jamstatic.fr/2016/04/01/jamstatic-fr/",
      "title": "Lancement de Jamstatic France",
      "summary": "Les actualités autour de la Jamstack et des générateurs de site statique",
      "date_published": "2016-04-01T00:00:00+00:00","content_text": "À propos de Jamstatic\nNous observons actuellement un vrai engouement pour l’utilisation des générateurs de site statique. Du fait de son utilisation par Github Pages, Jekyll est encore très populaire. Beaucoup d’autres générateurs comme Hugo ou Eleventy connaissent une forte progression. Sans parler de l'engouement autour de frameworks comme Next.js ou Nuxt.js.\nOn observe un bouillonnement aussi bien de la communauté qui a lancé de nombreux chantiers en parallèle que de nouveaux acteurs du monde de l’édition qui utilisent des SSG pour leurs publications.\nNous 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 la stack JavaScript, APIs &amp; Markup.\nActualités\nLes news, les conférences et appels à orateurs… tout ce qui peut toucher de près ou de loin la communauté. Rejoignez nos canaux Slack où nous relayons et commentons l'actualité autour de la Jamstack.\nRéférences\nIl 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.\nExemples de code\nÀ 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.\nNous 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.\nToute contribution est la bienvenue, nous sommes Jamstatic sur Github, jamstatic_fr sur Twitter et vous pouvez vous connecter sur notre canal de discussion Slack, si vous souhaitez interagir avec les membres de notre communauté.",
      "content_html": "<h2 id=\"a-propos-de-jamstatic\">À propos de Jamstatic</h2>\n<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>\n<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>\n<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>\n<h2 id=\"actualites\">Actualités</h2>\n<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>\n<h2 id=\"references\">Références</h2>\n<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>\n<h2 id=\"exemples-de-code\">Exemples de code</h2>\n<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>\n<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>\n<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>",
      "authors": [
        {
          "name": "frank"
        }
      ],
      "language": "fr"
    }
  ]
}
