Mise à jour du blog vers gridsome

Posté le 2. March 2021 dans Logiciels

J'avais migré en Janvier 2013 mon blog vers Pelican en venant de Dotclear. Pelican est un générateur de site statique en Python. J'avais alors dû faire l'impasse sur les commentaires mais au bénéfice d'un site performant et avec une surface d'attaque plus faible.

Plus tard, en décembre 2016, j'avais changé le thème pour alexandrevicenzi/Flex. Aujoud'hui je vais vous montrer la migration de mon blog vers Gridsome.

Je reste sur un générateur de site statique car, j'aime l'idée d'un site à la fois rapide et immuable tant que je ne décide pas de modifier le contenu mon blog. Alors pourquoi changer de Pelican vers Gridsome ?

Gridsome est un générateur de site statique écrit en Javascript. Il a la particularité de pouvoir générer des pages depuis des fichiers au format Markdown mais aussi depuis des CMS Headless, ou de n'importe quelles autres API et ce grace à une API commune en GraphQL.

En plus de générer le site statique au format HTML, Gridsome fonctionne comme une SPA1. Dans une SPA les pages sont générées côté client et sont mises à jour lors d'appel API pour récupérer les données. Ainsi seul le contenu modifié est rafraichit. Le framework Gridsome se base sur Vue.Js.

Ce qui m'a plus dans Gridsome, c'est l'utilisation de Vue.JS pour générer les pages statiques, mais aussi l'utilisation de GraphQL pour requêter le contenu des pages d'une même et unique façon.

Alternatives

Lors de la migration de mon blog, je voulais une technologie basée sur Vue.Js, car c'est un framework que j'apprécie. Mais le plus important, c'est que j'avais besoin de changement (pour le plaisir quoi ;)).

Les générateurs de site statique en Vue.Js sont :

Je vous laisse regarder et vous faire votre avis sur les différents framework. J'ai surtout testé VuePress mais j'ai changé pour Gridsome quand j'ai vu la simplicité d'implémentation.

Début de l'implémentation

Je voulais changer de technologie, mais j'aimais bien le thème Pelican que j'utilisais. Je n'avais donc pas envie de le changer. Je me suis imposé également les contraintes suivantes:

  • Je ne voulais pas changer les différents chemins d'accès au site (tant que faire se peut).
  • Je voulais garder les fonctionnalités suivantes:

    • Sitemap
    • Atom
  • J'avais des billets écrits en Markdown mais aussi 3 pages que je ne voulais pas réécrire en HTML
  • Garder le même thème
  • Avoir une page par catégorie et une page avec toutes les catégories
  • Avoir une page par tag et une page avec tous les tags

Je commence donc à initialiser un projet de zéro avec mes différents billets de blog, les pages (contact, anciens-projets, et contact) et les fichiers statiques (les images, les zips, ...).

J'ai du retravailler les différents billets de blog au format Markdown:

  • J'ai dû déplacer les différents attributs du fichier pour les placer dans un frontmatter au format YAML.
  • Les fichiers ont un nom qui porte de l'information. Ainsi, le dossier dans lequel se trouve le fichier constitue le nom de la catégorie, et le nom du fichier porte la date de publication et le slug (utilisé pour le chemin dans l'URL).

    Par exemple, avec le fichier: Logiciels/2009-02-15_debian-lenny-est-sortie.md.

    Avec le changement de paradigme de Gridsome, j'ai dû intégrer ces différentes informations dans le frontmatter.

  • J'ai également du retravailler les emplacements de fichiers qui étaient préfixés dans Pelican de |static|/public/.... Dans Gridsome, il n'y pas de syntaxe particulière, juste le nom du fichier absolu.

Avant j'avais:

Title: Debian Lenny est sortie
Tags: debian, kde

![Logo](|static|/public/Logiciels/debian-lenny-est-sortie/debian-logo.png)

et j'ai corrigé cela en

---
title: Debian Lenny est sortie
date: 2009-02-15
tags:
  - debian
  - kde
published: True
category: Logiciels
---

![Logo](/Logiciels/debian-lenny-est-sortie/debian-logo.png)

Afin de pouvoir reprendre mes fichiers au format markdown sans trop de modification, j'ai également installé les plugins remark suivants (ce sur quoi Gridsome se base pour transformer le Markdown en HTML) :

  • @gridsome/remark-prismjs: pour effectuer une coloration syntaxique.
  • remark-inline-links: pour pouvoir gérer les liens inclus (qui sont ensuite reportés en bas du fichier)

    [foo], [foo][], [bar][foo].
    
    ![foo], ![foo][], ![bar][foo].
    
    [foo]: http://example.com "Example Domain"
  • remark-attr: pour pouvoir ajouter des attributs à une image ou à un objet.

    ![alt](img){attrs} / ![alt](img){ height=50 }
  • remark-toc: pour pouvoir ajouter un sommaire à certain billet.
  • remark-footnotes: pour pouvoir ajouter les footnotes.

Création du thème

Le thème Pelican de mon blog est basé sur alexandrevicenzi/Flex. J'ai donc créé un thème gridsome en reprenant le style du thème précédent mais en convertissant les templates en Vue.Js.

J'ai donc créé ce thème que j'ai mis à disposition ici : phoenix741/gridsome-flex-markdown-starter.

Voici la configuration de gridsome que j'ai implémenté pour reconstruire le thème du blog:

  plugins: [
    {
      use: "@gridsome/source-filesystem",
      options: {
        baseDir: "content/posts",
        path: "**/*.md",
        typeName: "Post",
        refs: {
          tags: {
            typeName: "Tag",
            create: true,
          },
          category: {
            typeName: "Category",
            create: true,
          },
        },
      },
    },

Pour commencer, on utilise le module @gridsome/source-filesystem pour lire le contenu du dossier content/posts/**/*.md. On définit au plugin comment on créé les Tag et les Category.

    {
      use: "@microflash/gridsome-plugin-feed",
      options: {
        contentTypes: ["Post"],
        rss: {
          enabled: true,
          output: "/feed.xml",
        },
        atom: {
          enabled: true,
          output: "/feed.atom",
        },
      },
    },

Ensuite on crée un flux RSS et un flux ATOM avec ces posts, afin que les lecteurs qui utilisent encore un lecteur de flux, ou les programmes qui s'abonnent directement à mon site fonctionnent encore.

    {
      use: "@gridsome/source-filesystem",
      options: {
        baseDir: "content/pages",
        path: "*.md",
        typeName: "BlogPage",
      },
    },

On recommence avec les pages du site, que l'on type comme BlogPage. Ces pages ne seront pas accessibles via des catégories ou des pages. Il faudra faire des liens directement vers les pages depuis un menu.

    {
      use: "@gridsome/plugin-sitemap",
      options: {
        config: {
          "/post/*": {
            changefreq: "weekly",
            priority: 0.5,
          },
          "/page/*": {
            changefreq: "monthly",
            priority: 0.7,
          },
        },
      },
    },

Après, on définit un sitemap qui pourra être utilisé par les moteurs de recherches.

  ],
  templates: {
    Post: (obj) => {
      return (
        "/post/" + obj.fileInfo.name.replace(/^\d{4}-\d{2}-\d{2}[_\-]/, "")
      );
    },
    BlogPage: "/pages/:fileInfo__name",
    Tag: "/tag/:id",
    Category: "/category/:title",
  },

On définit les différentes chemins d'accès pour nos billets, nos pages, nos Tag, et nos Category. Comme je ne souhaite pas changer les adresses de mes billets sur mon site, j'ai défini les adresses sur les mêmes chemins que mon blog sous Pelican.

Comme les noms des fichiers des billets portent la date et le nom du billet, pour retrouver le nom du chemin d'origine, je supprime la date pour la génération du chemin.

  transformers: {
    remark: {
      plugins: [
        ["@gridsome/remark-prismjs", { showLineNumbers: true }],
        "remark-inline-links",
        ["remark-toc", { heading: "sommaire" }],
        "remark-attr",
      ],
      config: {
        footnotes: true,
      },
    },
  },

Enfin, je définis les différents plugins dont j'ai besoin pour que mes différents billets fonctionnent correctement.

  permalinks: {
    trailingSlash: "false",
    slugify: {
      use: "@sindresorhus/slugify",
      options: {
        decamelize: false,
      },
    },
  },
  css: {
    loaderOptions: {
      less: {
        // options here will be passed to less-loader
      },
    },
  },
};

Je termine par modifier la manière dont Gridsome transforme les adresses, toujours pour que les chemins de mon blog ne changent pas.

Développement des pages

Viens ensuite l'écriture des différentes pages, composants et layout pour définir notre thème. Le plus compliqué, c'est que le résumé (excerpt) fourni par Gridsome supprime le rendu HTML et les retours chariots.

Je voulais que sur la page principale de mon application, je puisse générer un résumé de chaque billet avec le texte et le rendu original du billet.

Pour cela malheureusement Gridsome ne fournit pas une manière simple de générer un résumé. Je vais vous présenter mon implémentation afin d'avoir un résumé au format Markdown.

Ma première version a été de créer à partir de contenu HTML le résumé dans un composant vue dédié. Lors de la génération des pages statiques, le résultat était comme attendu. Par contre lors du changement des pages (en mode SPA), le billet intégral du blog est chargé, pour être tronqué dynamiquement. Ce qui est long et pas performant quand cela arrive sur 10 billets à la suite. J'ai donc modifié le système pour générer les pages côté serveur.

Pour cela j'ai donc modifié le fichier gridsome.server.js. Le problème, c'est que Gridsome ne permet pas d'appeler un transformer (remark) directement, et ne permet pas de convertir un text en markdown (autrement qu'en passant par le plugin @gridsome/source-filesystem).

J'ai donc dû implémenter l'appel à remark (et les différents plugins) directement dans la partie serveur manuellement.

Une fois le fichier HTML obtenu, j'appelle une librairie dédiée truncatise afin de réduire le contenu à quelques paragraphes (4).

const truncatise = require("truncatise");

const remark = require("remark");
const html = require("remark-html");

const remarkPrism = require("@gridsome/remark-prismjs");
const remarkInlineLinks = require("remark-inline-links");
const remarkToc = require("remark-toc");
const remarkAttr = require("remark-attr");
const remarkFN = require("remark-footnotes");

module.exports = function(api) {
  api.loadSource(({ addSchemaResolvers }) => {
    addSchemaResolvers({
      Post: {
        excerpt: {
          type: "String",
          async resolve(obj) {
            return new Promise((resolve, reject) => {
              remark()
                // Appel des différents plugins utilisé coté config.
                .use(remarkPrism)
                .use(remarkInlineLinks)
                .use(remarkToc, { heading: "sommaire" })
                .use(remarkAttr)
                .use(remarkFN)
                // Génération HTML
                .use(html)
                .process(obj.content, function(err, file) {
                  if (err) {
                    return reject(err);
                  }

                  // On tronque en ne gardant que les 4 premiers paragraphes
                  resolve(
                    truncatise(String(file), {
                      TruncateLength: 4,
                      TruncateBy: "paragraphs",
                      Strict: false,
                      StripHTML: false,
                      Suffix: " ...",
                    })
                  );
                });
            });
          },
        },
      },
    });
  });
};

C'est ainsi que j'ai converti mon blog vers le framework Gridsome. Je vous passe tous les détails (vu que le thème est disponible sur Github sur phoenix741/gridsome-flex-markdown-starter).

Ajout d'un système de commentaire

Ce qu'il manquait sur mon blog précédent c'était un système de commentaires. J'ai donc ajouté ce système de commentaires à mon blog.

Je ne souhaitais pas ajouter n'importe quel système de commentaires. J'ai donc regardé ce que je pouvais ajouter sur un site statique. On va retrouver les systèmes de commentaires non libres, et faciles à implémenter (et pas forcément très compatible RGPD).

  • Disqus
  • Facebook comments
  • ...

De mon coté je me suis dirigé vers les systèmes de commentaires libres:

Remark42 nécessite l'installation d'un programme sur mon serveur. Je l'ai testé mais j'ai été à quelques problème lors de l'intégration avec Gridsome.

Vssue, Gitment et Utterances sont intéressants car ils permettent d'ajouter un système de commentaires se basant sur le système de ticketing de Github. Github n'est en lui même pas OpenSource mais les frameworks ci-dessus le sont.

Je considère que la plupart des personnes lisant mon blog ont un compte Github. Mais n'hésitez pas à commenter si ce n'est pas le cas ;).

Vssue était mon système préféré, beau, simple compatible avec Gitea (que j'utilise pour mes sources), Gitlab, et avec Github également. Mais dû aux limitations de Github et Gitea, il m'était nécessaire de fournir mon ClientSecret OAuth2.

Je n'ai pas apprécié cette idée mais j'ai tout de même voulu tester. Là je me suis rendu compte qu'il y avait des problèmes CORS. Le composant passe par CORS Anywhere mais ce dernier ne peut être utilisé que pour le développement. Ce qui signifie que je dois installer un proxy équivalent sur mon serveur. J'ai donc regardé pour autre chose.

J'ai alors testé Utterances. Le problème est que cette librairie n'est pas compatible avec les applications de type SPA. On peut voir d'ailleurs quelques commentaires sur le ticket Github #231 de personnes ayant tenté de faire une version du client pour Vue.Js.

Je me suis donc inspiré d'un composant Vue.JS écrit par quelqu'un d'autre (dans le 1er commentaire) pour inclure ce système sur mon site. Ce composant a dû être légèrement adapté pour fonctionner (je vous invite à regarder GithubComponent.vue).

Comme le plugin utilise directement l'objet window, il ne peut fonctionner avec le mode SSR (Server Side Rendering) de Vue.Js. J'ai dû donc désactiver le mode commentaires pour le côté SSR (la génération). Ce n'est pas déconnant car de toutes façon les commentaires sont gérés sur Github.

Voici comment on fait avec Gridsome. On commence par encadrer le bloc qui ne doit pas faire partie de la génération côté serveur (et qui est donc Client Only). Ce bloc contient notre composant GithubComponent.

<article>
  ...
  <ClientOnly>
    <div class="commentswrap">
      <div id="comments">
        <GithubComponent
          :title="$page.post.title"
          :repo="$page.metadata.utterances.repo"
          :pathname="this.$route.path"
          :url="url"
          :issueTerm="$page.metadata.utterances.issueTerm"
          :label="$page.metadata.utterances.label"
        ></GithubComponent>
      </div>
    </div>
  </ClientOnly>
</article>

Enfin on charge le composant dynamiquement dans la partie Javascript.

export default {
  components: {
    PostHeaderTitle,
    GithubComponent: () =>
      import("../components/GithubComponent")
        .then((m) => m.default)
        .catch(),
  },
  ...
}

Ces deux opérations permettent d'exclure cette partie lors de la génération des pages statique, mais de l'ajouter dynamiquement après le chargement de la page coté client.

Conclusion

Voici donc comment j'ai mis en place la version Vue.JS de mon blog. N'hésitez pas à m'envoyer un mail ou lâcher un commentaire.

Vous pouvez retrouver le kit de démarrage que j'ai fait pour mon blog sur github: phoenix741/gridsome-flex-markdown-starter. Vous pouvez également consulter l'intégration d'utterances sur un site en vue ici: phoenix741/gridsome-flex-markdown-starter:GithubComponent.vue.


  1. Single Page Application