A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Un coup d’œil sous le capot (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://blog.gandi.net/fr/posts/un-coup-d-oeil-sous-le-capot/">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>Un coup d’œil sous le capot</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://blog.gandi.net/fr/posts/un-coup-d-oeil-sous-le-capot/" title="Lien vers le contenu original">Source originale</a>
  70. </p>
  71. </nav>
  72. <hr>
  73. <p><img src="https://blog.gandi.net/fr/posts/un-coup-d-oeil-sous-le-capot/illustration.jpg" alt="Illustration d’ouvriers bâtissant un site web"></p>
  74. <blockquote>
  75. <p>« Tiens, Joachim, tu n’as pas encore grand chose à faire ? Bonne nouvelle, on a un projet pour toi. »</p>
  76. </blockquote>
  77. <p>C’est comme ça qu’Olivier, mon nouveau <del>boss</del> <del>manager</del> chef d’équipe, m’a présenté l’idée quelques jours après mon arrivée chez Gandi. L’équipe tech a envie de parler tech sur le web, et donc il faut mettre en place un blog.</p>
  78. <p>Comment est-ce qu’on fait un blog en 2022 ? J’admets que j’étais resté sur mon expérience de 2006 avec un <a href="https://www.gandi.net/fr/simple-hosting/wordpress">hébergement Wordpress</a>… mais le choix s’est fait autrement : pour limiter les besoins d’infrastructure et privilégier la performance, on voulait tester un générateur de site statique. Et comme peu de gens dans l’équipe font du Go, on en a choisi un en Go. Logique.</p>
  79. <h2 id="hugo">Hugo</h2>
  80. <p>Un générateur de site statique a une mission : à partir de contenus écrits dans des fichiers texte, il va générer des fichiers HTML qu’on pourra placer sur un serveur web. Un bon générateur va pouvoir permettre l’utilisation d’une taxonomie (les tags tout en bas 👇), la flexibilité des thèmes, et la rapidité de prise en main.</p>
  81. <p>N’ayant jamais touché à Hugo, c’est cette dernière qualité qui m’a impressionné. En deux temps et trois lignes de commande, c’était prêt… enfin, je veux dire, j’avais mon site en local avec un thème par défaut et un premier post en faux-latin. On était loin d’un site complet.</p>
  82. <p>Bon, et maintenant ?</p>
  83. <h2 id="la-structure-multi-langues">La structure multi-langues</h2>
  84. <p>Un des éléments dans le brief, c’était de pouvoir poster des contenus en français et en anglais. Pour ça, il faut paramétrer un peu Hugo.</p>
  85. <p>Tout d’abord, on a choisi une double arborescence, en utilisant <code>/fr/</code> et <code>/en/</code> pour différencier les langues. Les réglages sont explicites :</p>
  86. <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># config.toml</span>
  87. </span></span><span class="line"><span class="cl">
  88. </span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">languages</span><span class="p">]</span>
  89. </span></span><span class="line"><span class="cl"> <span class="p">[</span><span class="nx">languages</span><span class="p">.</span><span class="nx">en</span><span class="p">]</span>
  90. </span></span><span class="line"><span class="cl"> <span class="nx">title</span> <span class="p">=</span> <span class="s2">"Gandi Blog"</span>
  91. </span></span><span class="line"><span class="cl"> <span class="nx">languageName</span> <span class="p">=</span> <span class="s2">"English"</span>
  92. </span></span><span class="line"><span class="cl"> <span class="nx">weight</span> <span class="p">=</span> <span class="mi">1</span>
  93. </span></span><span class="line"><span class="cl"> <span class="nx">contentDir</span> <span class="p">=</span> <span class="s2">"content/en"</span>
  94. </span></span><span class="line"><span class="cl">
  95. </span></span><span class="line"><span class="cl"> <span class="p">[</span><span class="nx">languages</span><span class="p">.</span><span class="nx">fr</span><span class="p">]</span>
  96. </span></span><span class="line"><span class="cl"> <span class="nx">title</span> <span class="p">=</span> <span class="s2">"Blog Gandi"</span>
  97. </span></span><span class="line"><span class="cl"> <span class="nx">languageName</span> <span class="p">=</span> <span class="s2">"français"</span>
  98. </span></span><span class="line"><span class="cl"> <span class="nx">weight</span> <span class="p">=</span> <span class="mi">2</span>
  99. </span></span><span class="line"><span class="cl"> <span class="nx">contentDir</span> <span class="p">=</span> <span class="s2">"content/fr"</span>
  100. </span></span></code></pre></div>
  101. <p>La langue anglaise sera servie par défaut, mais il faudra préciser que même la langue par défaut devra être servie à partir de son répertoire, et non pas à partir de la racine. Pour ça :</p>
  102. <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># config.toml</span>
  103. </span></span><span class="line"><span class="cl">
  104. </span></span><span class="line"><span class="cl"><span class="nx">defaultContentLanguageInSubdir</span> <span class="p">=</span> <span class="kc">true</span>
  105. </span></span></code></pre></div>
  106. <p>Ça y est pour l’architecture. Pour ce qui est de l’affichage, Hugo prend tout en charge automagiquement : il faut bien utiliser les balises <code>i18n</code> pour traduire les morceaux d’interface dans la langue voulue. Les fichiers de traduction utilisent le format <code>.toml</code>, qui a l’avantage d’être lisible. Une différence lorsqu’on a dû gérer des grosses applications multilingues, c’est qu’il y a peu d’intégrations avec les outils de gestion de traduction… mais le scope d’un blog est tel que je ne pense pas qu’on dépassera la cinquantaine de termes différents à traduire.</p>
  107. <p>Quand deux posts de langue différente ont le même identifiant, ils sont considérés comme une traduction l’un de l’autre. J’ai voulu faire apparaître un lien au début du post pour indiquer où trouver la traduction :</p>
  108. <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- layouts/partials/page-translated-list.html --&gt;</span>
  109. </span></span><span class="line"><span class="cl">
  110. </span></span><span class="line"><span class="cl">{{ if .IsTranslated }}
  111. </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
  112. </span></span><span class="line"><span class="cl"> {{ range .Translations }}
  113. </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">lang</span><span class="o">=</span><span class="s">"{{ .Lang }}"</span><span class="p">&gt;</span>
  114. </span></span><span class="line"><span class="cl"> {{ i18n "translations" .Language.LanguageName }}
  115. </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ .Permalink }}"</span><span class="p">&gt;</span>{{ .Title }}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
  116. </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
  117. </span></span><span class="line"><span class="cl"> {{ end }}
  118. </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
  119. </span></span><span class="line"><span class="cl">{{ end }}
  120. </span></span></code></pre></div>
  121. <h2 id="la-recherche">La recherche</h2>
  122. <p>Le choix d’un site statique a provoqué une interrogation : comment est-ce qu’on va gérer la recherche interne ? Étant donné qu’il n’y a pas de back-end dynamique qui pourrait faire des requêtes en base de donnée (et surtout vu qu’on n’a pas de base de donnée), c’est une très bonne question.</p>
  123. <p>La recherche doit donc se situer du côté du client. Pour se faire, on a intégré l’utilitaire <a href="https://pagefind.app/">Pagefind</a>. Lorsque Hugo vient de compiler les contenus en pages HTML, on demande à Pagefind de faire un tour sur lesdites pages pour les indexer. Ensuite, l’utilitaire va générer un script JavaScript / WASM et des fichiers d’index, qui seront mis à disposition du visiteur comme le reste du site.</p>
  124. <p>Lorsque le visiteur va activer la recherche, le script se déclenchera et téléchargera les index au fur et à mesure du besoin.</p>
  125. <h2 id="les-styles">Les styles</h2>
  126. <p>Une autre nouveauté a été mise en place pour le développement de ce blog, elle est à découvrir du côté des styles.<br>
  127. Depuis peu, Pascal (le développeur responsable de l’intégration du Design System) travaille sur un système de <em>design tokens</em> pour faciliter les échanges et la passation du design, depuis les équipes de conception jusqu’aux équipes de développement.</p>
  128. <p>Pour vous proposer ses produits et services, les développeurs de Gandi sont répartis en plusieurs équipes : application de gestion de domaine, application pour les offres d’hébergement, les sites web, etc. Ces équipes ont besoin d’utiliser les mêmes éléments de design, pour maintenir l’unité de l’apparence des applications et sites web. C’est là que les design tokens montrent leur utilité : il s’agit d’informations sous forme de valeurs partagées entre les équipes. Ces tokens sont dans nos logiciels de conception graphique : un designer ne peut pas se tromper dans le choix d’une couleur ou d’une police ; ils commencent aussi à être testés dans nos apps et nos sites : ça permet aux développeurs d’avoir une seule source de vérité. Une fois le système mis en place, si la couleur principale de la marque Gandi vient à changer, il ne faudra mettre à jour qu’un fichier qui répercutera ces informations aux applications et aux sites web : le risque d’erreurs sera réduit et l’application de changements dans la charte graphique sera accélérée.</p>
  129. <p>Je disais plus haut que ça commence à être testé… en effet, le site que vous lisez utilise une version alpha de ces tokens. Ils couvrent les couleurs, les tailles de typos, les hauteurs de ligne, les dimensions, les espacements, les ombres… à l’heure où j’écris cet article, ce sont 200 valeurs qui sont définies.</p>
  130. <p>Ces valeurs m’ont été fournies par l’autorité centrale (enfin, par Pascal), au format CSS Custom Property. Mais pour nos projets front-end React il les exporte en variables JavaScript ou SASS. C’est là la force du système : quelle que soit la conception et le framework qui sous-tendent le projet, on peut récupérer ces <em>tokens</em> et les utiliser nativement.</p>
  131. <h2 id="contribution-et-déploiement">Contribution et déploiement</h2>
  132. <p>Toutes ces bases-là ne sont que des bases. L’important dans un blog, c’est d’avoir du contenu, et d’être visible en public.</p>
  133. <h3 id="permettre-à-léquipe-de-proposer-des-articles">Permettre à l’équipe de proposer des articles…</h3>
  134. <p>La gestion du contenu se fait de manière <em>flat-file</em>, c’est à dire que toutes les informations sont contenues dans des fichiers, contenus dans une arborescence de répertoires qui les structure. Pas de base de donnée, pas de complication.</p>
  135. <p>J’écris actuellement dans un fichier Markdown. Je pourrais aussi écrire dans tout un tas d’alternatives—AsciiDoc, reStructuredText, Pandoc ou HTML—et je ne doute pas qu’une ou deux personnes dans l’équipe vont contribuer en Org Mode, un format utilisé avec Emacs. Hugo pourra ensuite convertir tous ces types de contenus en pages web, générer les index, les pages d’archives, etc.</p>
  136. <p>Étant donné qu’on utilise des fichiers texte, le moyen le plus pratique pour ouvrir la contribution à toute l’équipe, c’est de mettre le site en commun dans un dépôt de code. Avec Gitlab, la forge logicielle qu’on utilise, on pourra contribuer directement depuis le web, grâce à l’interface de développement intégrée.</p>
  137. <p>Ces aspects techniques peuvent être un frein à la contribution, donc j’ai documenté les diverses étapes pour proposer un article. Sans ça, l’opportunité de contribuer est freinée par la difficulté de le faire.</p>
  138. <p>Une fois l’article sur le dépot Git, le reste de l’équipe va pouvoir le relire, proposer des modifications si c’est nécessaire, et le valider.</p>
  139. <h3 id="et-garder-le-processus-de-déploiement-le-plus-simple-possible">…et garder le processus de déploiement le plus simple possible</h3>
  140. <p>L’automatisation est une bien belle chose. Depuis quelques années, les forges logicielles permettent de déclencher des actions lorsque des changements sont faits à la base de code. Ces actions peuvent être très complexes, et ont le doux nom de CI/CD (Continuous Integration/Continuous Deployment). Elles nous donnent la possibilité de tester le code, la conformité de celui-ci par rapport aux règles de style de l’équipe, de tester les fonctionnalités une à une, les tester conjointement dans un environnement de production… puis si les tests sont valides, ces actions vont nous permettre de déployer le code sur les serveurs de production.</p>
  141. <p>Une fois le nouvel article sur le dépôt de code, les actions de CI/CD vont :</p>
  142. <ol>
  143. <li>créer une machine virtuelle et y installer Hugo et les autres outils nécessaires à…</li>
  144. <li>…compiler les contenus pour en faire un site web, comme décrit dans la partie précédente,</li>
  145. <li>pousser le site web vers le serveur.</li>
  146. </ol>
  147. <p>Comme ça, une à deux minutes après la relecture et validation par l’équipe, le site est en ligne, et visible par le public, en HTML et en RSS.</p>
  148. <h2 id="conclusion">Conclusion</h2>
  149. <p>En tant que premier projet public depuis mon arrivée chez Gandi, je suis content d’avoir pu tester tant de nouvelles chose—nouvelles pour moi, nouvelles pour l’équipe… la possibilité d’apprendre des nouveaux concepts et des nouvelles pratiques, c’est l’une des principales raisons pour laquelle je pratique ce métier. L’autre raison c’est que ça me permet de réaliser des choses pratiques et utiles dans le respect des personnes qui les utiliseront. J’espère avoir rempli ces missions, aussi bien pour mes nouveaux et nouvelles collègues que pour vous qui lisez cet article.</p>
  150. </article>
  151. <hr>
  152. <footer>
  153. <p>
  154. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  155. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  156. </svg> Accueil</a> •
  157. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  158. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  159. </svg> Suivre</a> •
  160. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  161. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  162. </svg> Pro</a> •
  163. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  164. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  165. </svg> Email</a> •
  166. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  167. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  168. </svg> Légal</abbr>
  169. </p>
  170. <template id="theme-selector">
  171. <form>
  172. <fieldset>
  173. <legend><svg class="icon icon-brightness-contrast">
  174. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  175. </svg> Thème</legend>
  176. <label>
  177. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  178. </label>
  179. <label>
  180. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  181. </label>
  182. <label>
  183. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  184. </label>
  185. </fieldset>
  186. </form>
  187. </template>
  188. </footer>
  189. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  190. <script>
  191. function loadThemeForm(templateName) {
  192. const themeSelectorTemplate = document.querySelector(templateName)
  193. const form = themeSelectorTemplate.content.firstElementChild
  194. themeSelectorTemplate.replaceWith(form)
  195. form.addEventListener('change', (e) => {
  196. const chosenColorScheme = e.target.value
  197. localStorage.setItem('theme', chosenColorScheme)
  198. toggleTheme(chosenColorScheme)
  199. })
  200. const selectedTheme = localStorage.getItem('theme')
  201. if (selectedTheme && selectedTheme !== 'undefined') {
  202. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  203. }
  204. }
  205. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  206. window.addEventListener('load', () => {
  207. let hasDarkRules = false
  208. for (const styleSheet of Array.from(document.styleSheets)) {
  209. let mediaRules = []
  210. for (const cssRule of styleSheet.cssRules) {
  211. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  212. continue
  213. }
  214. // WARNING: Safari does not have/supports `conditionText`.
  215. if (cssRule.conditionText) {
  216. if (cssRule.conditionText !== prefersColorSchemeDark) {
  217. continue
  218. }
  219. } else {
  220. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  221. continue
  222. }
  223. }
  224. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  225. }
  226. // WARNING: do not try to insert a Rule to a styleSheet you are
  227. // currently iterating on, otherwise the browser will be stuck
  228. // in a infinite loop…
  229. for (const mediaRule of mediaRules) {
  230. styleSheet.insertRule(mediaRule.cssText)
  231. hasDarkRules = true
  232. }
  233. }
  234. if (hasDarkRules) {
  235. loadThemeForm('#theme-selector')
  236. }
  237. })
  238. </script>
  239. </body>
  240. </html>