<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Buzut</title>
  
  <subtitle>Keep updated</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://buzut.net/"/>
  <updated>2024-09-05T06:56:59.840Z</updated>
  <id>https://buzut.net/</id>
  
  <author>
    <name>Buzut</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Organiser la connaissance</title>
    <link href="https://buzut.net/organisation-de-connaissance/"/>
    <id>https://buzut.net/organisation-de-connaissance/</id>
    <published>2024-06-10T06:00:46.000Z</published>
    <updated>2024-09-05T06:56:59.840Z</updated>
    
    <content type="html"><![CDATA[<p>Ça fait déjà trois ans que je n’ai pas publié ici… cela m’amène à réfléchir aux lacunes du blog pour l’organisation de la connaissance et aux apport des bases de connaissances.</p><p>Je pense qu’il est inutile de présenter Wikipedia, l’encyclopédie libre en ligne. Au delà de sa gratuité, quatre choses la caractérise :</p><ul><li>elle est participative,</li><li>elle est simple de navigation,</li><li>elle traite de <strong>tous</strong> les sujets,</li><li>elle repose sur un logiciel open source.</li></ul><p>Partant de ce principe, pourquoi n’y a-t-il pas une adoption plus importante du wiki au lieu du blog ?</p><span id="more"></span><h2 id="Un-peu-de-contexte"><a href="#Un-peu-de-contexte" class="headerlink" title="Un peu de contexte"></a>Un peu de contexte</h2><p><a href="https://mto.bike">MTO</a>, ma nouvelle activité, comporte une partie informatique puisqu’il s’agit d’un e-commerce mais aussi et surtout une énorme partie d’ingénierie électrique et mécanique puisque nous concevons des pièces (batteries, pièces plastiques, pièces d’usinage…).</p><p>Évidemment, je suis derrière la partie ingénierie et comme dans tout domaine, on se forme sans cesse. D’où cette réflexion : la nature chronologique et thématique du blog n’est-elle pas un frein à l’organisation pérenne de la connaissance ?</p><p>Comme l’équipe grandit chez MTO, j’avais un besoin d’organiser les process et données de la société. Il me fallait une documentation bien organisée que chacun puisse consulter, chaque équipe traitant de sujets très différents (documentation d’assemblage de nos pièces, référence des matériaux, process d’assemblage des batteries…)</p><p>Le blog ne me semblait pas du tout adapté, et en recherchant des solutions plus proches de mes besoins, qu’elles en adoptent le nom ou pas (knowledge base), toutes semblaient être des sortes de Wiki.</p><h2 id="Limite-du-blog"><a href="#Limite-du-blog" class="headerlink" title="Limite du blog"></a>Limite du blog</h2><p>Premièrement, un blog est thématique, celui-ci, dédié à l’informatique, serait dénaturé si je commençais à y poster des articles sur l’électronique ou la science des matériaux.</p><p>Deuxièmement, un blog est chronologique, mis à part la barre de recherche ou les moteurs de recherche, le contenu ancien est difficile d’accès via la navigation du site.</p><p>Ceci s’explique par la nature du blog. Comme son nom l’indique, le <em>blog</em>, diminutif de <em>web log</em>, est destiné à publier des informations de manière chronologique sur un sujet donné, c’est un journal.</p><p>Un journal, qu’il soit de bord, de voyage ou de politique, traite d’un sujet thématique et n’a pas une vocation prioritaire à être actualisé une fois l’article publié.</p><p>La connaissance, elle, est évolutive, doit être maintenue à jour (évolution de l’état de l’art, de la technologie…) et on doit pouvoir facilement et rapidement s’y frayer un chemin.</p><p>Ces problèmes ne sont pas des défauts de l’outil “blog”, ce sont des signes que l’outil est inadapté à une tâche. Le log a été précurseur dans la démocratisation de la publication de contenu, cependant, comme chaque outil à la mode, il a été adopté de manière abusive.</p><p>On suit de très nombreux blogs dans des domaines variés, néanmoins, lorsque l’on recherche une source de référence, qu’il s’agisse de Wikipedia, du MDN ou d’une documentation officielle, cela prend presque toujours la forme d’une base de connaissance ou d’un wiki (c’est un peu la même chose).</p><h2 id="La-knowledge-base"><a href="#La-knowledge-base" class="headerlink" title="La knowledge base"></a>La knowledge base</h2><p>Le propre du <a href="https://fr.wikipedia.org/wiki/Wiki">Wiki</a> est q’il est collaboratif, je fais donc un abus de langage en utilisant ce terme, car le but recherché ici n’est pas forcément la collaboration, mais la <a href="https://fr.wikipedia.org/wiki/Gestion_des_connaissances">gestion de la connaissance</a>.</p><p>Cependant, un Wiki est juste un type particulier de base de connaissance. On peut utiliser ou non la partie collaborative et tous les outils commerciaux (Atlassian, Zendesk…) proposent la collaboration.</p><p>La connaissance est néenmoins tellement précieuse qu’il vaut mieux s’attacher à la garder ouverte et portable. Je n’ai rien contre les outils commerciaux, mais si la source est fermée et que vos besoins évoluent, mieux vaut un logiciel vous permettant de facilement tout exporter et transférer ailleurs.</p><p>D’ailleurs, le bon compromis si vous ne voulez pas vous encombrer d’héberger et de gérer votre knowledge base, c’est d’opter pour la version hébergée d’une solution open source !</p><h2 id="Les-solutions-open-source"><a href="#Les-solutions-open-source" class="headerlink" title="Les solutions open source"></a>Les solutions open source</h2><p>Je ne vais pas ici vous faire une liste exhaustive de tous les projets, il y en a de nombreux. Premièrement, si vous hébergez vous-même l’outil, c’est un plus d’en choisir un dans une technologie que vous maitrisez. </p><p>Dans mon cas, j’ai donc limité mes recherches aux langages PHP et Node.js, mais sachez qu’il en existe dans la plupart des langages web. En ce limitant à ces deux langages, on a déjà pas mal d’options.</p><p>Il ne nous reste plus qu’à faire notre choix en fonction des fonctions offertes par les différentes solutions ainsi qu’en fonction du look &amp; feel. Autant que possible, on doit avoir du plaisir à écrire et à lire la documentation !</p><h3 id="Solutions-PHP"><a href="#Solutions-PHP" class="headerlink" title="Solutions PHP"></a>Solutions PHP</h3><p>L’avantage du PHP c’est qu’on que c’est supporté absolument partout dans la mesure où le projet ne requiert pas des lib exotiques. Un plan mutualisé à 3€ peut faire l’affaire.</p><ul><li><a href="https://www.mediawiki.org/wiki/MediaWiki">MediaWiki</a> le logiciel derrière Wikipedia. Il a été développé en 2002 par des scientifiques et est surement à ce jour le logiciel le plus utilisé au monde pour les sites de gestion de connaissance. Il utilise MySQL, a un riche écocystème de plugins, dispose d’une documentation complète, adopte la syntaxe de Wikipedia (ou plutôt le contraire) et est disponible dans presque toutes les langues.</li><li><a href="https://www.dokuwiki.org/">DokuWiki</a> est ultra léger, ne nécessite pas de base de données, offre une recherche fulltext, dispose de nombreux thèmes, supporte plus de 50 langues, offre un système de contrôle d’accès ACL, possède un riche système d’extensions et adopte une syntaxe simple dérivée de Wikipedia.</li><li><a href="https://www.bookstackapp.com/">BookStack</a> offre une interface épurée, propose un thème classique et un sombre au choix de l’utilisateur, supporte de nombreuses langues, permet la rédaction Markdown ou WYSIWYG, supporte les diagrams.net et offre un système de commentaires. Il est basé sur MySQL, offre la recherche et intègre diverses méthodes d’authentification (GitHub, Google, LDAP…). C’est mon choix pour <a href="https://lib.buzut.net">ma documentation personnelle</a> car BookStack adopte une catégorisation originale : le contenu est organisé en rayons, livres, chapitres et pages.</li></ul><h3 id="Solutions-NodeJS"><a href="#Solutions-NodeJS" class="headerlink" title="Solutions NodeJS"></a>Solutions NodeJS</h3><p>Les solutions NodeJS sont légèrement plus complexes à héberger. En contreprtie, elles sont puissantes et plutôt orientées entreprise (ou pour un particulier avec des besoins avancés).</p><ul><li><a href="https://wiki.js.org/">Wiki.js</a> est une solution Wiki puissante à l’interface épurée qui permet la rédaction Markdown ou WYSIWYG. Conçue pour les entreprises (mais pas que), elle supporte PostgreSQL, MySQL, MS SQL et SQLite. Le logiciel supporte un grand nombre de méthodes d’authentification, offre un système avancé de gestion des droits, permet en option de confier la recherche à des moteurs spécialisés (Elastic, Algolia…) et offre un système de commentaires. C’est mon choix pour la documentation de MTO.</li><li><a href="https://www.getoutline.com/">Outline</a> s’adresse aussi aux équipes avec une interface très léchée et dispose d’une offre hébergée cloud. Pour l’installation auto-hébergée, il faut Postgres et Redis. La rédaction se fait en Markdown et on note la possibilité de rédiger en direct de manière collaborative. Outline offre aussi une totale intégration avec Slack, le multilangue, un système de commentaires et propose des applications natives (iOS, Android, Windows &amp; macOS).</li></ul><p>De nouveau, comme dit en introduction, ce n’est pas une liste exhaustive. Il s’agit plutôt d’un compte rendu des solutions notables que j’ai considéré pour mes propres besoins. Cela étant dit, à moins que vous cherchiez une solution dans un language de programmation spécifique, vous devriez avoir ici l’ensemble des outils pouvant répondre à vos besoins.</p><p>Le blog est-il donc terminé ? Évidemment non ! Le blog et le Wiki coexistent, le blog est le meilleur des outils pour véhiculer des opinions ou des retours d’expérience par exemple. Je continuerai donc à me servir de ce blog pour partager mes opinions, mes expériences et partis-pris sur des sujets de dev.</p>]]></content>
    
    <summary type="html">
    
      Alors que le blog est souvent choisi pour publier et organiser ses connaissance, découvrons la force du Wiki.
    
    </summary>
    
    
    
      <category term="Réflexions" scheme="https://buzut.net/tag/Reflexions/"/>
    
      <category term="Outils" scheme="https://buzut.net/tag/Outils/"/>
    
  </entry>
  
  <entry>
    <title>Les CHMOD pour l&#39;hébergement internet</title>
    <link href="https://buzut.net/les-chmod-pour-lhebergement-internet/"/>
    <id>https://buzut.net/les-chmod-pour-lhebergement-internet/</id>
    <published>2021-05-24T06:28:59.000Z</published>
    <updated>2024-09-05T06:56:59.836Z</updated>
    
    <content type="html"><![CDATA[<p>Les CHMOD, ça parait simple et complexe à la fois. D’ailleurs, la plupart du temps, les développeurs s’en préoccupent peu, ou pas du tout. Pourtant, une mauvaise configuration peut faire survenir des bugs dans de nombreuses applications. Plus grave, cette mauvaise configuration ne garantie pas une sécurité optimale et peut faciliter le hack de votre site et l’intrusion dans vos serveurs. Faisons donc un petit tour des bonnes pratiques.</p><span id="more"></span><h2 id="Petit-rappel-sur-les-chmod"><a href="#Petit-rappel-sur-les-chmod" class="headerlink" title="Petit rappel sur les chmod"></a>Petit rappel sur les chmod</h2><p>Révisons rapidement les chmod pour bien commencer, on a trois types de permissions, lesquelles peuvent être notées de manières symboliques par <code>x</code> (<strong>execute</strong>), <code>w</code> (<strong>write</strong>) ou <code>r</code> (<strong>read</strong>), ou de manière octale, respectivement 1, 2 et 4. Ces droits sont applicables aux fichiers comme aux répertoires :</p><ul><li>le <strong>droit d’exécution</strong>, noté x ou 1 ; appliqué à un fichier, il permet de l’exécuter (exécutable unix par exemple), pour les répertoires, ce droit permet d’entrer dans le répertoire, et par conséquent, s’il n’est pas accordé, empêche de lister son contenu ;</li><li>le <strong>droit d’écriture</strong>, noté w ou 2 ; il permet de modifier le fichier, ou pour un répertoire, d’en modifier le contenu (ajout/suppression de fichiers) ;</li><li>le <strong>droit de lecture</strong>, noté r ou 4 ; il permet de lire le contenu d’un fichier, ou appliqué à un répertoire, d’en lister le contenu.</li></ul><p>De plus, il faut savoir que ces droits s’appliquent à trois types d’utilisateurs :</p><ul><li>le <strong>propriétaire</strong> du fichier (nommé <strong>user</strong>, noté <code>u</code>) ;</li><li>le <strong>groupe</strong>, concerne tous les autres membres du groupe (un groupe peut rassembler plusieurs utilisateurs, il est nommé <strong>group</strong>, noté <code>g</code>) ;</li><li><strong>tous</strong>, concerne tous les utilisateurs (nommé <strong>others</strong>, noté <code>o</code>).</li></ul><p>Voici un petit tableau récapitulatif :</p><table><thead><tr><th>Droits</th><th>Chiffre</th><th>Calcul</th></tr></thead><tbody><tr><td>—</td><td>0</td><td>0 + 0 + 0</td></tr><tr><td>r–</td><td>4</td><td>4 + 0 + 0</td></tr><tr><td>-w-</td><td>2</td><td>0 + 2 + 0</td></tr><tr><td>–x</td><td>1</td><td>0 + 0 + 1</td></tr><tr><td>rw-</td><td>6</td><td>4 + 2 + 0</td></tr><tr><td>-wx</td><td>3</td><td>0 + 2 + 1</td></tr><tr><td>r-x</td><td>5</td><td>4 + 0 + 1</td></tr><tr><td>rwx</td><td>7</td><td>4 + 2 + 1</td></tr></tbody></table><h2 id="Les-bons-reglages-pour-l’hebergement"><a href="#Les-bons-reglages-pour-l’hebergement" class="headerlink" title="Les bons réglages pour l’hébergement"></a>Les bons réglages pour l’hébergement</h2><p>On doit maintenant se poser la question des chmod à attribuer à nos fichiers pour être dans des conditions de sécurité acceptables. Le mieux, c’est de tout mettre à <code>777</code>…</p><p><img src="/assets/commitstrip-chmod.jpg" alt="Le chmod 777 de commitstrip"></p><p>Vous l’aurez donc compris, le conseil du <code>chmod 777</code>, c’était une boutade.</p><p>Par défaut, les répertoires sont en général à <code>755</code> – ce qui autorise tout pour le propriétaire du fichier (premier chiffre), donne le droit à la lecture et à l’exécution (listage dans le cas des répertoires) pour les autres (4+1 pour le groupe [second chiffre] et les autres [troisième chiffre]) – et les fichiers à <code>644</code> – autorise la lecture et l’écriture pour le propriétaire (4+2) et la lecture uniquement pour les autres.</p><h3 id="Le-white-listing"><a href="#Le-white-listing" class="headerlink" title="Le white listing"></a>Le white listing</h3><p>La meilleur politique consiste, comme toujours en sécurité informatique, à ne rien autoriser mis à part ce qui est nécessaire. Vous pouvez donc limiter les autorisations à <code>555</code> (lecture et listage) pour les répertoires n’ayant pas de contenu ayant vocation à changer. Typiquement un répertoire contenant des images ou des fichiers HTML et/ou PHP. En ce qui concerne les fichiers, <code>444</code> (lecture seule) suffira pour leur majorité, tels que les images, les fichiers HTML, CSS, JavaScript, PHP…</p><section class="hint"><h1 id="Moteur-PHP"><a href="#Moteur-PHP" class="headerlink" title="Moteur PHP"></a>Moteur PHP</h1><p>Au cas où vous vous posiez la question : non, les fichiers PHP n’ont pas besoin d’être exécutable. C’est en effet le moteur PHP qui lit leur contenu et les exécute, mais ils ne sont pas eux-même des exécutables (la plupart du temps).</p></section><p>Vous modifierez au cas par cas les droits des fichiers et des répertoires s’ils ont besoin de droits supplémentaires. Par exemple dans le cas d’un  exécutable, il faudra au moins lui donner le droit d’exécution en plus de celui de lecture, donc <code>555</code> (ou <code>550</code> celon l’utilisateur qui lance l’exécutable).</p><p>De même, en ce qui concerne les fichiers de logs, n’oubliez pas de leur accorder le droit en écriture, sans quoi il ne loggeront pas grand chose… Concernant les répertoires, il faudra donner le droit d’écriture au propriétaire si le répertoire reçoit des uploads ou si PHP créé de nouveaux fichiers à l’intérieur par exemple, donc droits à <code>755</code>.</p><h2 id="Proprietaire-groupe-et-autres…"><a href="#Proprietaire-groupe-et-autres…" class="headerlink" title="Propriétaire, groupe et autres…"></a>Propriétaire, groupe et autres…</h2><p>On parle depuis tout à l’heure de permissions accordées à trois niveaux d’utilisateurs. Cela implique, vous l’avez compris, qu’il est d’une part important de bien configurer les droits, mais d’autre part, qu’il faut aussi veiller à ne pas mettre n’importe quel utilisateur en tant que propriétaire !</p><p>En effet, la plupart du temps, le propriétaire a <code>7</code> (tous les droits), donc si votre propriétaire est le serveur, Apache par exemple (en général c’est l’utilisateur <code>www-data</code> sur les Debian et dérivés), il pourra facilement modifier vos fichiers et cela facilitera grandement le travail d’un attaquant ayant trouvé une faille sur votre serveur.</p><p>Plusieurs politiques peuvent donc être mises en place. La plupart du temps, le propriétaire des répertoires et fichiers est un utilisateur distinct des processus Apache (ou autre serveur) et appartenant aussi à un autre groupe (<code>www-data</code> est aussi le groupe correspondant au processus Apache). Par exemple l’utilisateur Linux sous lequel est loggué le développeur.</p><p>On peut déclarer <code>root</code> comme propriétaire, seul <code>root</code> aura donc les droits de propriétaire (<code>root</code> a de toute façon déjà tous les droits sur l’ensemble des répertoires du serveur). Il faudra alors être <code>root</code> pour faire ce que les autres et/ou le groupe n’ont pas le droit de faire.</p><p>On peut aussi n’accorder que des droits succins au propriétaire – les mêmes que ceux des “autres” – dans ce cas, il faudra passer en <code>root</code> pour éditer, supprimer etc, le fichier/répertoire.</p><p>J’ai parlé ici des utilisateurs, mais la même logique s’applique bien entendu aux groupes, bien qu’ils ne soient souvent pas pris en compte dans l’hébergement, ne laissez pas à un groupe (<code>www-data</code> au hasard) la possibilité de faire n’importe quoi !</p><p>Pour toutes les commandes relatives à l’administration des droits, des utilisateurs et des groupes, je vous recommande mon article sur <a href="/101-commandes-indispensables-sous-linux/">l’administration des systèmes Linux</a>.</p><p>J’espère que ce petit article aura su vous éclairer sur la gestion des droits pour votre hébergement ! N’hésitez pas à me signaler toute erreur ou omission.</p>]]></content>
    
    <summary type="html">
    
      Les CHMOD définissent les droits des dossiers et fichiers sur les systèmes Linux. Leur mauvaise configuration facilite grandement la prise de contrôle d&#39;un site par un pirate. Il est donc essentiel de bien les configurer.
    
    </summary>
    
    
    
      <category term="Sécurité" scheme="https://buzut.net/tag/Securite/"/>
    
      <category term="Linux" scheme="https://buzut.net/tag/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Gérer les encodages de texte en JavaScript</title>
    <link href="https://buzut.net/gerer-les-encodages-de-texte-en-javascript/"/>
    <id>https://buzut.net/gerer-les-encodages-de-texte-en-javascript/</id>
    <published>2021-02-12T09:00:00.000Z</published>
    <updated>2024-09-05T06:56:59.836Z</updated>
    
    <content type="html"><![CDATA[<p>L’encodage du texte a toujours été plus complexe qu’il ne parait. ASCII, Latin 1, Mac OS Roman, ISO 8850-n, UTF-8… On a tendance à s’y perdre. J’y dédis <a href="/cours/computer-science/de-ascii-a-unicode">un article</a> de mon livre <a href="/cours/computer-science/"><em>Computer Science</em></a>.</p><p>Cette complexité est sans compter le fait que différents langages de programmation gèrent ces encodages différemment. Vous êtes un développeur moderne, vous vous dites peut-être que de nos jours, tout se passe en UTF-8 et qu’il est inutile de s’appesantir sur le sujet ? Grossière erreur !</p><p>Vous connaissez le JavaScript. On utilise la méthode <code>length</code> pour récupérer la longueur d’une chaine de caractère. Essayez donc <code>&#39;🤔&#39;.length</code>. Si vous pensiez à 1, vous êtes dans l’erreur.</p><p>Par ailleurs, saviez-vous que <code>&#39;A&#39; !== &#39;А&#39;</code> ? Oui Monsieur, parfaitement ! Le premier est le “a” majuscule latin tandis que le second est en cyrillique. Une vérification sans considération des noms d’utilisateurs d’un site par exemple, peut vite poser problème…</p><p>Et vous n’avez encore rien vu ! Convaincu de l’utilité de faire le tour de la question ? Allez, on se lance !</p><span id="more"></span><p>C’est certain, lorsque l’on fait du web, en ne s’encombre que très rarement de ces considérations. Tout est édité en UTF-8, tout est sauvegardé en UTF-8 et tout est rendu sur les pages en UTF-8. Pas problème.</p><p>Cet article plonge dans le détail de la gestion des encodages en JavaScript. Aussi, si les plans Unicode, la BMP ou les différents UTF-n sont des notions obscures pour vous, vous devriez lire rapidement le <a href="/cours/computer-science/encodage-du-texte-en-unicode">chapitre dédié à ce sujet</a> de mon livre <a href="/cours/computer-science/"><em>Computer Science</em></a>.</p><h2 id="Compter-les-caracteres"><a href="#Compter-les-caracteres" class="headerlink" title="Compter les caractères"></a>Compter les caractères</h2><p>On a vu en exemple que <code>length</code> ne tombe pas juste avec les Emojis. Il n’y a pas qu’eux. Cela vient du fait que <code>length</code> ne compte pas comme nous. Pour nous, <code>&#39;🤔&#39;.length</code> devrait faire 1 car “🤔” constitue un glyphe unique.</p><p>En JavaScript, les chaînes de caractères sont encodées en UTF-16, donc sur deux octets. Ainsi, pour <code>length</code>, tout ce qui fait deux octets ou moins est considéré comme de longueur 1, car cela constitue un mot UTF-16. Cependant, pour les caractères moins courants, ceux qui ne sont pas dans la table BMP, il faudra plusieurs octets pour les encoder, et c’est précisément le nombre de mots UTF-16 que nous retourne <code>length</code>.</p><p>De manière assez simple, si l’on veut que JavaScript compte “correctement” le nombre de glyphes, il faut utiliser l’itérateur de <code>String</code>.</p><pre><code class="js">Array.from(&#39;🤔&#39;).length // 1</code></pre><p>Voilà qui résout nos problèmes ! En résumé <code>length</code> compte le nombre de mots UTF-16 tandis que l’itérateur compte le nombre de points de code.</p><h2 id="L’habit-ne-fait-pas-le-moine"><a href="#L’habit-ne-fait-pas-le-moine" class="headerlink" title="L’habit ne fait pas le moine"></a>L’habit ne fait pas le moine</h2><p>Comme le dit le proverbe, on ne peut se fier aux apparences ! Deux caractères visuellement identiques peuvent être différents, par exemple <code>ê !== ê</code>. Nous sommes cette fois-ci en présence du caractère <em>Latin Small Letter E with Circumflex</em> pour le premier mais d’un ensemble de deux caractères pour le second :</p><ul><li>Latin Small Letter E –&gt; e</li><li>Combining Circumflex Accent –&gt; ̂</li></ul><p>Malheureusement, cette fois, le String iterator ne viendra pas à notre secours.</p><pre><code class="js">Array.from(&#39;ê&#39;).length // 2</code></pre><p>😭 ! C’est le cas de le dire 😝</p><p>Que pouvons-nous faire ? Si vous avez lu mon article sur Unicode, vous n’êtes pas sans savoir que le standard Unicode propose une notion d’équivalence afin de savoir si, quand bien même deux caractères sont différents, on peut les considérer comme équivalents.</p><p>C’est en effet le cas de l’exemple précédent. On pourrait pratiquement être tenté de penser qu’une comparaison sans vérification de type fonctionne :</p><pre><code class="js">* ê !== ê // false* ê != ê // true</code></pre><p>Ça n’est pas le cas, mais c’est un peu l’idée. Unicode fournit deux notions d’équivalence.</p><ul><li>L’équivalence canonique signifie que deux caractères sont équivalents visuellement et sémantiquement parfaitement identiques. C’est le cas de notre exemple. Ainsi, tout caractère pré-composé est canoniquement équivalent à sa forme composée.</li><li>L’équivalence de compatibilité est moins stricte mais permet d’effectuer des comparaisons, notamment en permettant l’usage de jeux de caractères plus restreints. Cela permettra par exemple de comparer les ligatures à leur équivalent non-lié (“…” à “…”), ou encore les chiffres en indice ou exposant à la version <em>normale</em>…</li></ul><p>L’équivalence canonique est un sous-ensemble plus strict de l’équivalence de compatibilité. Par conséquent, toute séquence canonique est aussi compatible.</p><p>Pour comparaison, Unicode définit quatre formes normales, deux sont des formes canoniques (NFx) et deux sont des formes de compatibilité (NFKxx), chacune d’elle offrant la forme composée et la pré-composée.</p><dl>    <dt>NFD</dt><dd><i>Normalization Form Canonical Decomposition</i>. Les caractères sont convertis dans leur équivalent composé.</dd>    <dt>NFC</dt><dd><i>Normalization Form Canonical Composition</i>. C'est l'inverse de la précédente, les caractères sont convertis dans leur équivalent pré-composé.</dd>    <dt>NFKD</dt><dd><i>Normalization Form Compatibility Decomposition</i>. Les caractères sont décomposés par équivalence canonique et de compatibilité, et sont réordonnés.</dd>    <dt>NFKC</dt><dd><i>Normalization Form Compatibility Composition</i>. Les caractères sont décomposés par équivalence canonique et de compatibilité, sont réordonnés et sont composés par équivalence canonique.</dd></dl><p>Quelques exemples.</p><pre><code class="js">const composed = &#39;ê&#39;; // U+0065 U+0302const preComposed = &#39;ê&#39;; // U+00EAconst realExp = &#39;n²&#39;;const fakeExp = &#39;n2&#39;;console.log (composed === preComposed); // falseconsole.log(composed.normalize(&#39;NFC&#39;) === preComposed); // trueconsole.log(preComposed.normalize(&#39;NFD&#39;) === composed); // trueconsole.log(realExp.normalize(&#39;NFKC&#39;) === fakeExp); // true</code></pre><p>La normalisation nous permet de trier, rechercher et comparer. Elle offre donc de grands services.</p><h2 id="Quand-y’a-probleme"><a href="#Quand-y’a-probleme" class="headerlink" title="Quand y’a problème"></a>Quand y’a problème</h2><p>La normalisation nous rend de bons services… mais elle ne résout pas tout non plus. Quelle que soit la normalisation utilisée, certains caractères proches, visuellement ou sémantiquement, ne sont pas compatibles.</p><p>En exemple, nous pouvons citer des lettres similaires d’alphabets différents, <code>A !== А</code> ; certaines ligatures <code>œ !== oe</code> mais encore des signes enregistrés comme points de code différents pour des raisons variées <code>α !== ⍺</code>.</p><p>Dans ce dernier exemple, nous sommes en présence de la lettre grecque alpha (U+03B1) pour le premier, et du signe mathématiques alpha (U+237A) pour le second.</p><p>C’est un peu la jungle car bien que le signe Micro “μ” (U+03B5) ne soit pas égal à la lettre grecque Mu “µ” (U+03BC), elle est équivalente, mais ceci n’est pas vrai pour les deux alpha.</p><pre><code class="js">const greakAlpha = &#39;α&#39;;const scienceAlpha = &#39;⍺&#39;;const greakMicro = &#39;µ&#39;;const scienceMicro = &#39;μ&#39;;console.log(greakAlpha.normalize(&#39;NFKC&#39;) === scienceAlpha.normalize(&#39;NFKC&#39;)); // falseconsole.log(greakMicro.normalize(&#39;NFKC&#39;) === scienceMicro.normalize(&#39;NFKC&#39;)); // true</code></pre><p>Cette règle est dûe au fait que le signe Micro était déjà présent dans la table Latin-1, tandis que dans ce jeux de caractère, alpha n’était pas une option, version grecque ou scientifique.</p><p>Pour palier à ces problèmes, le groupe Unicode publie <a href="https://www.unicode.org/Public/security/latest/confusables.txt">une table</a> des signes qui peuvent être confondus. C’est assez fastidieu à gérer manuellement, nous en conviendrons.</p><p>Il existe donc un <a href="https://github.com/codebox/homoglyph">module JavaScript</a> qui reprend la table en question et nous offre une fonction de comparaison. Tous les signes sont listés dans le fichier <code>chars</code> du code source. À titre d’exemple, voici la <a href="https://github.com/codebox/homoglyph/blob/master/raw_data/chars.txt#L67">ligne 67</a>, des “a”, .</p><pre><code class="js">aɑαа⍺ａ𝐚𝑎𝒂𝒶𝓪𝔞𝕒𝖆𝖺𝗮𝘢𝙖𝚊𝛂𝛼𝜶𝝰𝞪</code></pre><p>Rien d’extrêmement complexe en soit, mais nous sommes fort reconnaissants à ce module de nous dispenser de cette fastidieuse tâche. Il n’est pas conçu pour directement comparer deux lettres, mais pour vérifier sur une chaîne n’est pas semblable à ensemble d’autres chaînes de caractères.</p><pre><code class="js">const homoglyphSearch = require(&#39;homoglyph-search&#39;);const userNames = [&#39;Antoine&#39;, &#39;Charlotte&#39;, &#39;Luc&#39;];console.log(homoglyphSearch.search(&#39;αntoine&#39;, userNames));// [ &#39;Antoine&#39;, &#39;Charlotte&#39;, &#39;Luc&#39; ]// [ &#123; match: &#39;αntoine&#39;, word: &#39;Antoine&#39;, index: 0 &#125; ]</code></pre><p>Quoi qu’il en soit, gardez à l’esprit que vous ne pourrez jamais être sur à 100% de votre comptage et de vos comparaisons. D’autant plus avec le système combinatoire de l’Unicode.</p><pre><code class="js">🤦🏼‍♂️ // 5 points Unicode, 17 octets (U+1F926 U+1F3FC U+200D U+2642 U+FE0F)🤦🏼‍♂ // 4 points Unicode, 14 octets (U+1F926 U+1F3FC U+200D U+2642)</code></pre><p>Il est quasi certain que ces deux glyphes s’affichent de manière identique sur votre système. Ils sont pourtant composés de manière différente. Autant donc se résigner et accepter que tout n’est pas sous votre contrôle.</p><h2 id="Representer-un-caractere-en-JavaScript"><a href="#Representer-un-caractere-en-JavaScript" class="headerlink" title="Représenter un caractère en JavaScript"></a>Représenter un caractère en JavaScript</h2><p>Vous le savez peut-être, mais nous avons plusieurs moyens de représenter un caractère en JavaScript. La manière de faire la plus courante est de simplement utiliser la séquence d’octets du caractère. Cette manière est totalement transparente pour nous car il suffit d’entrer le caractère souhaité.</p><p>L’autre façon est d’utiliser une séquence d’échappement. Le JavaScript en compte quatre :</p><ul>    <li>séquence octale,</li>    <li>séquence hexadécimale,</li>    <li>séquence Unicode,</li>    <li>séquence points de code Unicode.</li></ul><p>Quelles différences ? Les deux premières permettent de représenter les 256 caractères de la table ASCII, mais l’octale est dépréciée en faveur de l’hexadécimale. Prenons “@” comme exemple. Un rapide coup d’œil à la <a href="https://ascii-table.net">table ASCII</a> nous apprends que “@” porte le point de code 64. Soit 100 en octal et 40 en hexadécimal.</p><pre><code class="js">// Les séquences octales comportent de 2 à 4 caractères// Il est possible de forcer 4 caractères par des zéro à gauche// Cela permet d&#39;éviter des confusion si plusieurs séquences se suiventconsole.log(&#39;\100&#39;); // @// Les séquences hexadécimales sont toujours de quatre caractères// Elle commence toujours par &quot;x&quot; pour hexaconsole.log(&#39;\x64&#39;); // @</code></pre><p>Ces deux premières séquences sont assez peu utilisées car assez limitantes. Les deux autres options offrent bien plus de possibilités. La séquence Unicode reprend l’encodage UTF-16. Il est donc possible de représenter tous les caractères de la BMP avec une séquence, et les autres caractères en combinant deux séquences avec le mécanisme de <em>surrogate pairs</em> de l’UTF-16.</p><p>Les séquences de points de code Unicode permettent, quant à elles, de représenter tout caractère de la table directement grâce à son numéro Unicode.</p><p>Voyons maintenant d’autres exemples avec un caractère plus cool : 😎</p><pre><code class="js">// Utilise directement la séquence d&#39;octetsconsole.log(&#39;😎&#39;); // 😎// Séquence Unicode UTF-16,// on utilise ici le mécanisme de surrogate pour les caractères nécessitant plus de deux octetsconsole.log(&#39;\uD83D\uDE0E&#39;); // 😎// On précise ici le numéro unicode directement entre \u&#123;UNICODE_NUMBER&#125;console.log(&#39;\u&#123;1F60E&#125;&#39;); // 😎</code></pre><p>Dans ces deux derniers encodages, vous avez certainement noté que nous avons le “u” signifiant unicode. Dans les cas où le caractère représenté est dans la BMP, les deux séquences seront très similaires car le numéro Unicode est égal à son encodage UTF-16. Reprenons notre exemple précédent.</p><pre><code class="js">// Séquence hexadécimaleconsole.log(&#39;\x40&#39;); // @// Séquence Unicode hexa/UTF-16, longueur fixe de 6 caractèresconsole.log(&#39;\u0040&#39;); // @// Séquence numéro de code Unicode// La taille est ici variable, les zéro non-significatifs sont facultatifsconsole.log(&#39;\u&#123;40&#125;&#39;); // @</code></pre><h2 id="Creer-un-caractere-a-partir-de-points-de-code"><a href="#Creer-un-caractere-a-partir-de-points-de-code" class="headerlink" title="Créer un caractère à partir de points de code"></a>Créer un caractère à partir de points de code</h2><p>Pour créer un ou des caractères à partir d’un ou plusieurs points de code, nous avons deux méthodes à notre disposition :</p><ul><li><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/fromCharCode"><code>String.fromCharCode</code></a> permet de créer un ou plusieurs caractères à partir d’un point ou suite de points UTF-16</li><li><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/fromCodePoint"><code>String.fromCodePoint</code></a> permet de créer un ou plusieurs caractères à partir d’un point ou suite de point Unicode.</li></ul><p>La seconde méthode est un apport de l’ES6. La majeure différence entre les deux est que <code>fromCodePoint</code> permet de créer des caractères n’appartenant pas à la BMP directement à partir de leur point de code Unicode. <code>fromCodePoint</code> devra obligatoirement recourir aux <em>surrogate pairs</em> pour représenter des caractères hors de la BMP.</p><pre><code class="js">console.log(String.fromCharCode(0xD83D, 0xDE0E)); // 😎console.log(String.fromCodePoint(0x1F60E)); // 😎</code></pre><h2 id="Recuperer-un-point-de-code-a-partir-d’une-chaine"><a href="#Recuperer-un-point-de-code-a-partir-d’une-chaine" class="headerlink" title="Récupérer un point de code à partir d’une chaîne"></a>Récupérer un point de code à partir d’une chaîne</h2><p>Il s’agit là de l’opération inverse de la précédente. Nous avons également deux méthodes nous permettant d’effectuer cette action :</p><ul><li><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/charCodeAt"><code>charCodeAt</code></a> retourne un entier (décimal) compris entre 0 et 65535 qui correspond au code UTF-16 d’un caractère de la chaîne situé à une position donnée.</li><li><a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/codePointAt"><code>codePointAt</code></a> retourne un entier (décimal) qui correspond au code Unicode du caractère de la chaîne à la position donnée.</li></ul><p>De même que <code>fromCodePoint</code>, <code>codePointAt</code> est un ajout de l’ES6. Il prend donc en charge l’Unicode tandis que <code>charCodeAt</code> ne retournera que le point de code correspondant à une des surrogates s’il s’agit d’un caractère n’appartenant pas à la BMP.</p><p>Vous notez par ailleurs que ces deux méthodes retournent les points de code en décimal. On travaille le plus couramment en hexa, on utilisera donc <code>toString</code> pour immédiatement récupérer les valeurs en hexa.</p><pre><code class="js">// Le résultat retourné est le même avec des caractères de la BMPconsole.log(&#39;😎&#39;.charCodeAt(&#39;@&#39;).toString(16)); // 40console.log(&#39;😎&#39;.codePointAt(&#39;@&#39;).toString(16)); // 40// On récupère ici le premier surrogateconsole.log(&#39;😎&#39;.charCodeAt(&#39;0&#39;).toString(16)); // d83d// Et le second à l&#39;index 1, le caractère encodé en UTF-16 est bien D83D DE0Econsole.log(&#39;😎&#39;.charCodeAt(&#39;0&#39;).toString(16)); // de0e// Ici on obtient directement le point de code Unicode du caractèreconsole.log(&#39;😎&#39;.codePointAt(&#39;0&#39;).toString(16)); // 1f60e</code></pre><h2 id="Naviguer-entre-les-encodages"><a href="#Naviguer-entre-les-encodages" class="headerlink" title="Naviguer entre les encodages"></a>Naviguer entre les encodages</h2><p>Il y a quelques temps, j’ai travaillé sur une API de SMS marketing. Vous l’ignorez peut-être, mais lorsque le SMS a été inventé, l’UTF-8 n’était pas encore trop à la mode, et il ne l’est toujours pas dans le monde du SMS. En front, vous travaillez donc en UTF-8, vous devez quand-même gérer le comptage des caractères et encoder le tout en back…</p><p>Sans oublier que vous recevez aussi des SMS. Vous devez interpréter l’encodage, le convertir en UTF-8 avant de le stocker et de l’afficher. C’est là qu’on réalise tout l’intérêt de maîtriser un minimum le sujet.</p><section class="hint"><h1>Encodage des SMS</h1>Dans le monde du SMS, aujourd'hui encore, la table d'encodage la <a href="https://en.wikipedia.org/wiki/GSM_03.38">table GSM-7</a>.<p>Dans cette table, tout est encodé sur 7 bits, comme au tout début de l’ASCII. Nous sommes d’accord, cela ne fait pas beaucoup de caractères. C’est pourquoi il est possible pour les téléphones modernes d’utiliser l’UCS-2. Ce dernier est un encodage de longueur fixe sur deux octets.</p><p>Il est le premier à avoir été normalisé par le consortium Unicode. L’UCS-2 est aujourd’hui déprécié et n’est plus en usage en dehors de la téléphonie. L’inconvénient de UCS-2 est que chaque caractère est encodé sur deux octets, donc cela prend nettement plus d’espace. Par ailleurs, il ne permet de représenter que les 65k caractères de la BMP, ce qui est aujourd’hui restrictif.</p><p>Malgré cela, vous réalisez que vous et moi, envoyons et recevons constamment des messages avec des caractères en dehors de la BMP : nos fameux Emojis qui font bien plus de deux octets ! L’UCS-2 est un sous-ensemble de l’UTF-16BE.</p><p>En pratique, l’UCS-2 est très peu supporté. Ainsi, côté logiciel, tous les smartphones décodent les SMS UCS-2 avec l’algorithme UTF-16. De ce fait, tout UCS-2 valide est décodé correctement, mais c’est aussi le cas de messages encodés en UTF-16, bien que techniquement, ils ne respectent pas le standard.</p></section><p>Le problème est posé. On doit compter correctement le nombre de caractères, jongler entre plusieurs encodages, envoyer dans un encodage mais stocker dans un autre. À tout cela s’ajoute une doc lacunaire quand elle n’est pas tout bonnement fausse !</p><h3 id="Premier-contact"><a href="#Premier-contact" class="headerlink" title="Premier contact"></a>Premier contact</h3><p>Je n’y connais alors rien au standard SMS et je lis machinalement la doc de la société qui fait transiter nos SMS. La doc mentionne le fait que la table est retreinte, mais explique qu’il est possible “d’encoder en Unicode”.</p><blockquote><p>L’élément <code>&lt;binary&gt;</code> est utilisé pour le contenu du message à la place de l’élément <code>&lt;text&gt;</code> dans le cas où le message doit être envoyé en unicode.</p></blockquote><blockquote><p><code>&lt;binary&gt;</code> peut avoir un attribut unicode<br><code>unicode=&quot;1&quot;</code> qui précise que les octets dans le champ binary sont codés sous le forme hh : Le caractère Q est par exemple codé 0051 (en Unicode un caractère est codé sur 2 octets)<br><code>unicode=&quot;2&quot;</code> qui précise que les octets dans le champ binary sont codés sous le forme %hh : Le caractère Q est par exemple codé %00%51 (en Unicode un caractère est codé sur 2 octets)</p></blockquote><p>Tout va bien, Unicode est un <em>encodage</em> qui fonctionne sur deux octets. Les mecs connaissent leur sujet 🤣</p><p>Fort heureusement, un petit tour sur la Unicode table nous apprend que “Q” vaut <code>0051</code> quand il est encodé en UTF-16BE. Ça tombe bien, ça matche avec l’UCS-2.</p><p>On a plus loin un autre exemple :</p><blockquote><p>Que je m’aime ! 😂<br>Encodé en Unicode binary <code>0051007500650020006a00650020006d002700610069006d0065002000210020d83dde02</code></p></blockquote><p>Oui, vous remarquez qu’on a une chaîne de caractère “binaire” qui contient en fait de l’hexa. Le binaire ne contient que des 0 et des 1, comme chacun sait. Donc la chaîne en question encodée en UTF-16BE et représentée en binaire, ça donne plutôt ça.</p><pre><code>0101000100000000011101010000000001100101000000000010000000000000011010100000000001100101000000000010000000000000011011010000000000100111000000000110000100000000011010010000000001101101000000000110010100000000001000000000000000100001000000000010000011011000001111011101111000000010</code></pre><p>Le décor est planté : on est face à un sujet pas évident et rien d’autre pour nous épauler qu’une doc d’amateur écrite par une personne pas très au fait des standards.</p><p>Grâce aux deux exemples, on sait néanmoins que si on sort de la table GSM, on peut compter sur l’UTF-16 – et pas l’UCS-2 – car il nous est possible d’utiliser des Emojis. L’UCS-2 nous limiterait à la BMP, et nous priverait de nos chers Emojis. On sait dès lors que l’on peut encoder tous les caractères Unicode.</p><p>Cela mène cependant à un nouveau détail. La doc nous affirme que “en Unicode un caractère est codé sur 2 octets”. De nouveau, on flaire l’ignorance du rédacteur de la documentation. En effet, si “Q” est bien encodé sur deux octets (00 51), “🤣” en requiert déjà quatre (D8 3D DE 02), soit deux mots UTF-16. Ce que nous confirme notre <code>length</code> favori.</p><pre><code class="js">&#39;🤣&#39;.length // 2</code></pre><p>Deux points de code de deux octets chacun : 2 x 2 = 4. Tout caractère n’est donc pas en “Unicode” encodé sur deux octets. Ou alors, il faut redéfinir le terme octet !</p><p>On sait donc qu’à défaut de pouvoir faire confiance à la doc, on peut compter sur <code>length</code> pour évaluer le nombre de “caractères” que contient un SMS. Profitons-en d’ailleurs pour un nouvel exercice de comptage.</p><pre><code class="js">&#39;👨‍👩‍👧‍👦&#39;.length // 11</code></pre><p>Avec une confusion entre point de code et caractère, on comprend rapidement que la factuaration risque d’être sport, mais c’est un autre débat…</p><p>Ce que l’on sait de manière certaine, c’est que cette chimère encodée sur deux octets est de l’UTF-16BE. De ce fait, le comptage des caractères peut s’effectuer directement en front, c’est assez simple.</p><ul><li>On se constitue un array avec tous les caractères valides dans la table GSM.</li><li>On normalise le texte entré en NFD. Ainsi tous les caractères sont dans leur forme composée, moins de caractères à envoyer et plus de chance de matcher la table GSM.</li><li>Si tout matche, chaque caractère compte pour 1 et le SMS peut contenir 160 caractères. Sinon, on encode en UCS-2/UTF-16 et le SMS ne peut plus contenir que 70 caractères (ils sont encodés sur 16 bits et non plus 7).</li><li>Si le SMS dépasse le nombre de caractères que peut contenir un seul message, on utilise un <a href="https://en.wikipedia.org/wiki/User_Data_Header">User Data Header</a> spécifiant que le message est composé de plusieurs segments. Ce header prend de la place, ainsi, il ne nous reste plus que 153 caractères par message en encodage GSM et 67 en UCS.</li></ul><pre><code class="javascript">const maxGSMChars = 160;const maxUCSChars = 70;const multiSegmentGSMChars = 153const multiSegmentUCSChars = 67// On normalise l&#39;inputconst normalisedMsg = inputMsg.normalize(&#39;NFD&#39;);// Notre fonction vérifie si tous les caractères contenus dans normalisedMsg appartiennent à la table GSM// elle retourne un boolconst encodingType = checkGSMCompatibility(normalisedMsg) ? &#39;GSM&#39; : &#39;UCS&#39;;// Dans les deux cas, String.length correspond maintenant à notre définition de longueur de caractèrelet numberOfSegments;if (      (encodingType === &#39;GSM&#39; &amp;&amp; normalisedMsg.length &lt;= maxCharsPerMsg)      || (encodingType === &#39;GSM&#39; &amp;&amp; normalisedMsg.length &lt;= maxCharsPerMsg)    ) &#123;    numberOfSegments = 1;&#125;else &#123;    const charsPerSegment = encodingType === &#39;GSM&#39; ? multiSegmentGSMChars : multiSegmentUCSChars;    numberOfSegments = normalisedMsg.length / charsPerSegment;&#125;</code></pre><h2 id="Transcoding"><a href="#Transcoding" class="headerlink" title="Transcoding"></a>Transcoding</h2><p>Le front a fait son boulot et il n’est pas démesurément complexe. Côté back, nous avons un peu de pain sur la planche.</p><p>Premièrement, on peut réutiliser l’algo précédent pour vérifier si oui ou non, le texte du SMS peut être envoyé en mode texte directement avec la table GSM. Si oui, nous communiquons le texte à l’API de l’opérateur sans autre forme de procès.</p><p>Bien qu’il en soit proche, le texte ainsi envoyé n’est pas dans l’encodage de la table GSM à proprement parler, il est en UTF-8 (car c’est dans cet encodage que nous avons receuillis les données sur notre page web).</p><p>Vous le savez peut-être (si vous avez vu mon le <a href="/cours/computer-science/encodage-du-texte-en-unicode#Differentes-representations-d’un-caractere">chapitre sur Unicode de mon livre </a>), l’UTF-8 est rétro-compatible avec l’ASCII. Donc tout texte ASCII est un texte UTF-8 valide.</p><p>Bien que très proche de l’ASCII, la table GSM n’est pas exactement la même. Ce n’est pas à nous de nous occuper de cette conversion, mais si cela avait été le cas, il aurait simplement fallu réencoder quelques caractères. Le “@” a par exemple comme valeur 40 en ASCII et 00 en GSM 03.38.</p><h3 id="Encoder-en-UTF-16"><a href="#Encoder-en-UTF-16" class="headerlink" title="Encoder en UTF-16"></a>Encoder en UTF-16</h3><p>Le plus gros du travail consistera à encoder les messages qui doivent être envoyés en UTF-16. Notre texte est en interne stocké en UTF-16 par JavaScript, mais nous travaillons avec des séquences d’octets de manière totalement transparente. Il va falloir forcer la conversion en UTF-16 et récupérer une représentation en hexa.</p><p>Les différentes méthodes que nous avons précédemment vu nous permettent d’effectuer cette conversion sans aucune difficulté.</p><pre><code class="js">function encodeToUTF16(message) &#123;    // On procède octet par octet    return message.split(&#39;&#39;).map((char) =&gt; &#123;        // Pour chaque octet, on récupère sont point de code        const word = char.codePointAt(0).toString(16);        // Si le point de code ne fait qu&#39;un seul octet, on ajoute des 0        // Ceci permet d&#39;obtenir la longueur fixe de l&#39;UTF-16        if (word.length === 2) return `00$&#123;word&#125;`;        return word;    &#125;)    .join(&#39;&#39;);&#125;</code></pre><p>On utilise ici <code>codePointAt</code> mais on pourrait tout aussi bien utiliser <code>charCodeAt</code> étant donné que l’on itère  octet par octet et non par point de code Unicode.</p><h3 id="Decoder-du-Latin-1-URL-encoded"><a href="#Decoder-du-Latin-1-URL-encoded" class="headerlink" title="Décoder du Latin-1 URL encoded"></a>Décoder du Latin-1 URL encoded</h3><p>Là vous vous dites très certainement quelque-chose dans le genre de WTF. Celui-ci est pour le lulz.</p><p>En effet, lorsqu’un SMS envoyé reçoit une réponse, celle-ci nous est retournée sur un endpoint de notre choix, via une requête <code>GET</code>. <code>POST</code> aurait été plus indiqué, mais pour une raison que j’ignore c’est du <code>GET</code> et tout est passé en paramètre de l’URL.</p><pre><code class="js">// On a donc un endoint appelé de la sorte/sms/response/?FROM=tel&amp;MESSAGE=msg&amp;RET_ID=campaignId</code></pre><p>Rien de bien sorcier pensez-vous. Cependant, <code>msg</code> peut contenir toute sorte de caractères. C’est un SMS, il est donc en théorie soit au format GSM, soit en UCS-2… Néanmoins, comme les données ne sont pas envoyés en <code>POST</code> mais passées directement en paramètres, il n’est pas possible de spécifier l’encodage utilisé.</p><p>La <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a> précise que pour les URL, tout caractère réservé ou qui sort de la table ASCII doit être encodé en UTF-8 puis mis au format URL.</p><blockquote><p>When a new URI scheme defines a component that represents textual<br>   data consisting of characters from the Universal Character Set [UCS],<br>   the data should first be encoded as octets according to the UTF-8<br>   character encoding [STD63]; then only those octets that do not<br>   correspond to characters in the unreserved set should be percent-<br>   encoded.  For example, the character A would be represented as “A”,<br>   the character LATIN CAPITAL LETTER A WITH GRAVE would be represented<br>   as “%C3%80”, and the character KATAKANA LETTER A would be represented<br>   as “%E3%82%A2”.</p></blockquote><p>L’URL encoding est assez simple, tout caractère réservé ou qui n’est pas ASCII, est représenté par la valeur de l’octet en hexadécimal. Bien entendu, cette valeur varie selon l’encodage utilisé. C’est pourquoi, comme il n’y a pas moyen de savoir quel encodage est utilisé, le standard dit de toujours les considérer comme étant de l’UTF-8.</p><pre><code class="js">console.log(encodeURIComponent(&#39;😎&#39;)); // %F0%9F%98%8E</code></pre><p>La valeur de “😎” est bien <code>F0 9F 98 8E</code> en UTF-8. En UTF-16BE, ce serait <code>D8 3D DE 0E</code>. D’où l’importance de bien respecter le standard, sans quoi, on doit jouer aux devinettes.</p><p>Notre opérateur farceur, dans sa grande créativité, a décidé que ce serait plus drôle d’encoder en Latin-1 avant d’effectuer l’URL encoding. Évidemment, la doc reste muette à ce sujet – sinon ce ne serait pas drôle – ce qui évidemment faisait planter l’API lorsque des caractères non-ASCII devaient être décodés.</p><p>En effet, <code>decodeURIComponent</code> retourne une erreur si l’encodage est invalide. Je me retrouve donc obligé de parser et décoder manuellement les paramètres GET retournés par notre cher opérateur.</p><p>Par <em>chance</em>, les octets du Latin-1 matchent avec les code points Unicode, la conversion est donc assez aisée.</p><p>Par exemple, l’apostrophe droit, dont le code unicode est <em>U+0027</em>, vaut <code>27</code> en hexa du Latin-1, et sera donc encodée <code>%27</code> avec l’URL encoding. Vous pouvez consulter la table Latin-1 sur le <a href="https://cs.stanford.edu/people/miles/iso8859.html">site de Standford</a>.</p><p>Pour récupérer automatiquement le bon caractère en JavaScript, on utilise une simple REGEX pour obtenir la valeur après le “%”, puis on fait la correspondance directement avec la fonction <code>fromCharCode</code>.</p><p>Cette fonction retourne le caractère correspondant à un point de code Unicode. Cependant, elle retourne un codepoint en décimal et non en hexadécimal. Il suffit pour cela d’utiliser <code>parseInt</code> et le tour est joué. Voici donc le code correspondant :</p><pre><code class="js">function decodeLatin1URIComponent(str) &#123;    str.replace(/\+/g, &#39; &#39;).replace(/%([a-f0-9]&#123;2&#125;)/gi, (m, m1) =&gt; String.fromCharCode(parseInt(m1, 16)));&#125;</code></pre><p>Vous l’avez peut-être remarqué, on remplace les “+” par des espaces avant la conversion. En effet, le “+” est un caractère réservé qui compte pour un espace dans la norme URL encoding. On le remplace donc avant la conversion, car après, on ne serait plus en mesure de savoir s’il s’agit d’un plus “espace” ou réellement du signe “+”.</p><h2 id="Encoder-en-UTF-8"><a href="#Encoder-en-UTF-8" class="headerlink" title="Encoder en UTF-8"></a>Encoder en UTF-8</h2><p>L’UTF-8 est un encodage de taille variable. Il y a plusieurs moyens d’y parvenir en JavaScript. Si l’on est dans un environnement dans lequel le JavaScript moderne est supporté, aucun problème.</p><pre><code class="js">const encoder = new TextEncoder();const utf8Arr = encoder.encode(&#39;😎&#39;);console.log(utf8Arr); // 240 159 152 142</code></pre><p>Et voilà, le tour est joué. Il est possible que vous souhaitiez travailler en hexa, on va se faire une petite fonction pour ça.</p><pre><code class="js">function encodeToUnicodeUtf8(str) &#123;    const encoder = new TextEncoder();    const utf8Arr = encoder.encode(str);    return utf8Arr.reduce((acc, curr) =&gt; &#123;        acc.push(curr.toString(16));        return acc;    &#125;, []);&#125;console.log(encodeToUnicodeUtf8(&#39;😎&#39;)); // F0 9F 98 8E</code></pre><p>Voilà qui est mieux. Maintenant, admettons que vous désiriez une solution qui ne s’appuie pas sur les toutes dernières API ? Il y a la <a href="https://github.com/google/closure-library/blob/master/closure/goog/crypt/crypt.js#L114">méthode de Google</a>. Elle consiste à comparer chaque point de code au plan Unicode auquel il appartient et de l’encoder en fonction de sa place.</p><p>Il y a une intéressante discussion sur <a href="https://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array">StackOverflow</a> avec plusieurs implémentations à ce sujet.</p><p>Cependant, il y a une troisième voie. Peut-être que la partie précédente sur l’URL encoding vous a inspiré. On va pouvoir <em>tricher</em> un peu en s’épaulant de l’URL encoding. Nous avons <code>encodeURIComponent</code> dont nous avons précédemment parlé, mais aussi <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI"><code>encodeURI</code></a>.</p><p>Contrairement au premier, <code>encodeURI</code> n’encode pas les caractères réservés. Il encode donc les espaces et les caractères n’appartenant pas à l’ASCII. Voici donc mon implémentation rapide d’un encodeur UTF-8 basé sur <code>encodeURI</code>.</p><pre><code class="js">function encodeToUnicodeUtf8(str) &#123;    if (!str.length) return [];    const url = encodeURI(str);    const safeString = url.replace(/%20/g, &#39; &#39;); // On remet l&#39;espace comme un espace    const utf8Arr = [];    let multiByte = false;    let multiByteChar = &#39;&#39;;    Array.from(safeString).forEach((str) =&gt; &#123;        // S&#39;il y a un % c&#39;est qu&#39;on part sur du multibyte encodé par encodeURI        if (str === &#39;%&#39;) multiByte = true;        // Premier caractère de l&#39;octet        else if (multiByte &amp;&amp; !multiByteChar) multiByteChar = str;        // Second caractère de l&#39;octet encodé        else if (multiByte) &#123;            utf8Arr.push(multiByteChar + str);            multiByteChar = &#39;&#39;;            multiByte = false;        &#125;        // Le caractère n&#39;a pas été encodé, on récupère son codepoint        else utf8Arr.push(str.charCodeAt().toString(16));    &#125;);    return utf8Arr;&#125;</code></pre><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>J’espère que cet article vous a permis de comprendre l’importance de l’encodage dans la gestion du texte et des communications inter-programmes. Si vous souhaitez encore approfondir la question, je vous recommande la lecture de deux articles : <a href="https://hsivonen.fi/string-length/">It’s Not Wrong that “🤦🏼‍♂️”.length == 7</a> et <a href="https://mathiasbynens.be/notes/javascript-unicode">JavaScript has a Unicode problem</a>.</p><p>On a vite tendance à oublier les subtilités de la gestion des différents encodages dès que l’on n’a plus à le gérer explicitement. Aussi, n’hésitez pas à le mettre en favoris pour vite y revenir quand le besoin se présentera 😉</p>]]></content>
    
    <summary type="html">
    
      Lorsqu&#39;on a besoin de gérer gérer l&#39;encodage de texte,  on est un peu perdu car c&#39;est transparent 99% du temps Découvrons sa gestion en JavaScript !
    
    </summary>
    
    
    
      <category term="JavaScript" scheme="https://buzut.net/tag/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Sites statiques et Jamstack : la révolution frontend</title>
    <link href="https://buzut.net/sites-statiques-et-jamstack-la-revolution-frontend/"/>
    <id>https://buzut.net/sites-statiques-et-jamstack-la-revolution-frontend/</id>
    <published>2020-10-18T22:00:00.000Z</published>
    <updated>2024-09-05T06:56:59.840Z</updated>
    
    <content type="html"><![CDATA[<p>Les générateurs de sites statiques (GSS ou communément nommés <abbr title="static site genrator">SSG</abbr>), sont devenus en quelques années, grâce aux apports de la Jamstack, des outils de premier plan. D’une solution pour blog de développeur, ces technologies propulsent aujourd’hui des sites à fort trafic et à forte valeur ajoutée.</p><p>Spotify, Mastercard, Nike, Google, Facebook, Airbnb… Ce ne sont que quelques-uns des utilisateurs de ces solutions. Quels en sont donc les avantages, qu’est-il possible de faire avec cette nouvelle stack et comment ? Autant de questions auxquelles nous allons répondre dans cet article.</p><span id="more"></span><h2 id="Un-peu-d’histoire"><a href="#Un-peu-d’histoire" class="headerlink" title="Un peu d’histoire"></a>Un peu d’histoire</h2><p>Lorsque Tim Berners-Lee créé le web en 1989, il n’existe rien d’autre que des sites statiques : pour créer un site, il faut manuellement écrire chacune des pages en HTML.</p><p>Rapidement, des chercheurs du <a href="https://fr.wikipedia.org/wiki/National_Center_for_Supercomputing_Applications">National Center for Supercomputing Applications</a> se penchent sur deux problèmes :</p><ul><li>Comment faire afin de n’écrire qu’une seule fois des éléments qui se répètent sur toutes les pages ?</li><li>Est-il possible de dynamiquement générer du HTML ?</li></ul><p>C’est en 1993 qu’ils créent un serveur web, <a href="https://fr.wikipedia.org/wiki/NCSA_HTTPd">NCSA HTTPd</a>, avec deux technologies majeures :</p><ul><li>les <a href="https://fr.wikipedia.org/wiki/Server_Side_Includes">Server Side Includes</a> permettent de construire un document HTML à partir de plusieurs fichiers en permettant d’inclure le HTML d’un fichier dans un autre. Ainsi, le code du header et du footer (et tout autre élément redondant) n’est plus à répéter sur toutes les pages.</li><li>la <a href="https://fr.wikipedia.org/wiki/Server_Side_Includes">Common Gateway Interface</a> (ou CGI) permet la communication du serveur web avec des programmes externes qui peuvent lire les requêtes reçues par le serveur et générer du HTML à envoyer au client.</li></ul><p>Le web dynamique était né. Ce n’est qu’un an plus tard, en 1994, que Rasmus Lerdorf créé un langage spécifiquement dédié à la génération dynamique de pages HTML, le <a href="https://fr.wikipedia.org/wiki/PHP">PHP</a>. Les premiers générateurs de sites statiques grand public ne se font pas attendre très longtemps, <a href="https://fr.wikipedia.org/wiki/Microsoft_FrontPage">FrontPage</a> et <a href="https://fr.wikipedia.org/wiki/Adobe_Dreamweaver">Dreamweaver</a> arrivent respectivement en 1995 et 1997.</p><p>Viennent ensuite les CMS, <a href="https://fr.wikipedia.org/wiki/TYPO3">TYPO3</a> débarque en 1998, rapidement suivi par de nombreux autres : SPIP, Dotclear, Drupal, WordPress, Joomla… Les CMS vont dominer le paysage du développement web pendant près de 15 ans. Qu’il s’agisse d’un site vitrine ou d’un site e-commerce, le CMS est l’outil de choix dans près de 99% des cas.</p><h3 id="La-LAMP-Stack"><a href="#La-LAMP-Stack" class="headerlink" title="La LAMP Stack"></a>La LAMP Stack</h3><p>Je me souviens de mes débuts sur le web. J’étudiais le HTML sur le Site du Zéro et le cours terminait pas un chapitre sur les formulaires. Après avoir appris les différents champs existants, les méthodes <code>POST</code> et <code>GET</code> et l’utilité de <code>target</code>, le cours concluait donc :</p><blockquote><p>Vous savez envoyer des données via un formulaire. Il faut maintenant apprendre un langage backend comme PHP pour les récupérer et les traiter, sinon ça ne sert à rien.</p></blockquote><p>Gros éléctrochoc. C’était l’époque du “Web 2.0” et je réalisais que le seul moyen pour moi de ne pas rester bloqué au stade 1.0 était de maîtriser un langage backend… j’étais parti dans l’apprentissage de PHP, puis de MySQL.</p><figure><img src="/assets/lamp-stack.png" alt="schéma de la pile lamp"><figcaption>La stack classique avec rendu dynamique côté serveur</figcaption></figure><p>C’était il y a plus d’une décennie. À l’époque, la stack LAMP (Linux, Apache, MySQL, PHP) était reine. D’autres stack existent, mais toutes présentent le même schéma de fonctionnement :</p><ol><li>le navigateur du client demande une page au serveur web,</li><li>le serveur demande à son tour la page au moteur de rendu dynamique (PHP, Java…),</li><li>le moteur interroge la base de données,</li><li>le moteur génère le HTML à partir des données récupérées,</li><li>le moteur transmet les données au serveur web,</li><li>le serveur web envoie les données au client.</li></ol><p>Tous les CMS fonctionnent de cette manière, il s’agit du <em>one best way</em> en matière de programmation et d’hébergement. C’est le seul moyen de bénéficier des fonctions modernes du web.</p><p>Évidemment, générer les pages à chaque fois qu’un utilisateur la demande consomme beaucoup de ressources et n’est donc pas scalable. Si vous avez déjà utilisé WordPress par exemple, chacun sait qu’il y a de nombreux plugins de cache.</p><p>La première fois qu’une page est consultée, elle est dynamiquement générée, puis le HTML généré est stocké et sera directement servi au prochain visiteur, sans re-passer par la case génération dynamique.</p><figure><img src="/assets/lamp-caching.png" alt="schéma de la pile lamp"><figcaption>LAMP stack avec système de cache</figcaption></figure><p>Cela fonctionne pour les pages dont le contenu est majoritairement statique, mais ce n’est pas valable pour les site proposant un compte utilisateur dont le contenu est unique à chaque visiteur. Dans ce cas là, on utilise un cache partiel et on ajoute un cache à la base de données pour ne pas requêter les mêmes informations encore et encore.</p><p>Évidemment, pour améliorer les performances, on stocke les fichiers statiques (CSS, JavaScript, images) sur un CDN. On peut aussi y placer les fichiers pseudo-statiques : les pages HTML qui ne sont pas générées à la volée (celles qui ne changent que quelques fois par jour ou moins).</p><figure><img src="/assets/lamp-cache-cdn.png" alt="schéma de la pile lamp"><figcaption>Mise en place d'un CDN et deux niveaux de cache</figcaption></figure><p>Toute cette infrastructure est complexe. Il faut s’assurer que la bonne version des fichiers est servie depuis le cache, effectuer les mises à jour de sécurité, veiller à ce que toutes les briques de la pile soient fonctionnelles, ajouter des serveurs pour encaisser l’augmentation du trafic etc.</p><figure><img src="/assets/lamp-cache-challenges.png" alt="schéma de la pile lamp"><figcaption>Cette stack complexe nécessite des compétences dans de nombreux domaines</figcaption></figure><p>On arrive à un stade où il faut être ingénieur fullstack pour le moindre projet d’envergure. Même pour un simple blog sous WordPress, il faut continuellement se soucier des mises à jour, de la compatibilité des différents plugins, du thème etc.</p><p>Autant dire qu’une certaine <em>stack fatigue</em> finit par se faire sentir. Les développeurs veulent développer et non passer leur temps à faire de l’ops et de l’adminSys.</p><h3 id="Le-retour-des-SSG"><a href="#Le-retour-des-SSG" class="headerlink" title="Le retour des SSG"></a>Le retour des SSG</h3><p>Les générateurs de sites statiques (<abbr title="static site generaror">SSG</abbr> ou GSS en français) ne sont pas nouveaux. En plus des générateurs grand public déjà mentionnés, ils sont largement utilisé par les développeurs afin de générer la documentation logicielle.</p><p>Les générateurs de documentation produisent le HTML à partir du code source. Le plus ancien SSG destiné à la documentation est <a href="https://fr.wikipedia.org/wiki/Javadoc">JavaDoc</a>, sorti en 1995. <a href="https://fr.wikipedia.org/wiki/Doxygen">Doxygen</a> arrive quelques années plus tard et supporte de nombreux langages. Enfin, bien <a href="https://en.wikipedia.org/wiki/Comparison_of_documentation_generators">bien d’autres</a> sont spécifiques à leur propre langage.</p><p>C’est cependant en 2008 que l’usage des SSG explose avec les <a href="https://pages.github.com/">GitHub pages</a>. Cette <em>feature</em> de GitHub permet de publier des sites directement depuis le dépôt Git. Il suffit pour cela de glisser/déposer un site pré-construit (fichiers statiques) ou d’utiliser Jekyll, le SSG créé par le co-fondateur de GitHub.</p><iframe class="video" height="450" src="https://www.youtube-nocookie.com/embed/2MsN8gpT6jY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><p>Il était possible d’utiliser un SSG avant les GitHub Pages et de simplement déployer sur le cloud, sans avoir à se soucier de maintenance et de sécurité. Néanmoins, avec les GH Pages, les développeurs accèdent à une approche résolument moderne : terminé les déploiements via FTP ou rsync, on n’utilise désormais plus que Git et tout est automatisé  avec la CI/CD.</p><p>S’en suit alors une explosion des générateurs de site statique. Les blogs de développeurs migrent par milliers des CMS aux SSG. Le site statique devient une mode et il se créé des SSG toutes les semaines, même <a href="https://wp2static.com/">WordPress possède sont SSG</a>.</p><p>Aujourd’hui, l’environnement est mature, chaque langage présente un choix d’outils confortable. Développeur Python, Ruby, Java, Go ou JavaScript ? Aucun problème. Barbu ne jurant que par le C ou le Rust ? T’inquiète. Sysadmin amoureux du Bash ? On a ce qu’il te faut !</p><p>Bien qu’il ne soit absolument pas nécessaire de connaître le langage dans lequel un SSG est programmé pour l’utiliser, de nombreux développeurs apprécient de pouvoir l’étendre ou le modifier au besoin (même s’il ne le font jamais…).</p><p>Pour trouver le SSG qui saura combler vos besoins, direction le site <a href="https://jamstack.org/generators/">Jamstack.org</a>, où l’ensemble des SSG disponibles sont référencés et triables par langage et moteur de template.</p><h3 id="La-puissance-des-navigateurs"><a href="#La-puissance-des-navigateurs" class="headerlink" title="La puissance des navigateurs"></a>La puissance des navigateurs</h3><p>Dans les années 90 et dans le début des années 2000, les navigateurs étaient de simples lecteurs de documents. Toute l’interactivité devait donc se passer côté serveur.</p><p>Cependant, avec l’émergence de l’Ajax dans les années 2000 apparaissent les <a href="https://fr.wikipedia.org/wiki/Application_web_monopage">SPA</a> et les <a href="https://fr.wikipedia.org/wiki/Application_web">WebApps</a>, lesquelles sont des sites web qui ne nécessitent pas de rechargement de page et offrent une expérience identique à un logiciel natif.</p><figure><img src="/assets/interface-google-docs.png" alt="schéma de la pile lamp"><figcaption>De Gmail à Docs, les webapps Google représentent bien les possibilités offertes par les navigateurs</figcaption></figure><p>On pense immédiatement aux applications web de Google (Gmail, Google Docs…), mais les exemples d’applications web ne manquent pas. Sans aucune exécution côté serveur, vous pouvez par exemple <a href="https://squoosh.app/">compresser vos images</a>, vos <a href="https://jakearchibald.github.io/svgomg/">SVG</a> ou gérer vos <a href="http://todomvc.com/examples/mithril/#/">listes de tâches</a>.</p><p>Les fonctions jusque là réservées aux applications natives, plein écran, hors connexion, notifications, fonctionnement en arrière plan, accès au GPS, au Bluetooth etc, sont aujourd’hui regroupées sous le terme <a href="https://web.dev/what-are-pwas/"><abbr title="Progressive web app">PWA</abbr></a>.</p><h2 id="Jamstack-le-steroide-du-SSG"><a href="#Jamstack-le-steroide-du-SSG" class="headerlink" title="Jamstack, le stéroïde du SSG"></a>Jamstack, le stéroïde du SSG</h2><p>Le titre parle de “révolution frontend”, mais jusque là, rien de bien nouveau me direz-vous : les développeurs frontend peuvent faire des sites statiques… c’est ce qu’ils ont toujours fait.</p><p>Site statique ou non, on a tout de même certains besoins – immédiatement identifiés ou qui arrivent au fil de l’eau – les commentaires, la recherche, éditer sans passer par un éditeur de code etc.</p><p>La Jamstack arrive à une période où l’on migre un peu tout vers le cloud et c’est en réalité bien de cela qu’il s’agit. L’idée de la Jamstack est que l’on veut se concentrer sur le front (d’où le lien avec le site statique) et que l’on confie le reste à des services cloud (recherche, paiement, base de données, authentification…).</p><section class="hint"><h1 id="Definition-de-la-Jamstack"><a href="#Definition-de-la-Jamstack" class="headerlink" title="Définition de la Jamstack"></a>Définition de la Jamstack</h1><p><img src="/assets/jamstack-definition.png" alt="Schéma de la JAMstack"></p><p>Le terme a été créé par Mathias Biilmann, le fondateur de Netlify, un hébergeur cloud dédié aux sites statiques et à la Jamstack. La plupart des images précédentes sont d’ailleurs tirées des <a href="https://speakerdeck.com/biilmann/the-jam-stack">slides</a> d’une de <a href="https://vimeo.com/163522126">ses conférences</a>.</p><blockquote><p>JAM signifie JavaScript, APIs et Markup. La Jamstack désigne tous les sites/webapps dont le contenu est pré-généré (processus de build ou pages statiques) à partir de Markup (Markdown, JSON, HTML) et dont l’interactivité repose exclusivement sur le JavaScript côté client et l’usage d’APIs.</p></blockquote><p>Tout site de la Jamstack a comme point commun de ne pas dépendre d’un langage côté serveur et d’être pré-compilé. Un <a href="https://jamstack.org/">site dédié</a> est créé pour évangéliser la communauté et expliquer les mérites de cette nouvelle stack.</p></section><p>Bien entendu, une stack LAMP classique reposait aussi souvent sur des services externes. Par exemple, un WordPress/WooCommerce utilisera un service de paiement externe (que l’on appelle cela cloud ou pas, il ne sera donc pas complètement autonome).</p><p>La différence est qu’en mode Jamstack, on pousse l’externalisation à l’extrême : il n’y a plus de serveur du tout et plus vraiment de backend. On utilisera peut-être une base de données (cloud) pour stocker les produits et leurs prix (certains stockent tout directement via l’API Stripe), les comptes clients pourront être gérés via des fonctions serverless, les emails seront envoyés via Sendinblue ou Mailchimp etc.</p><p>Évidemment, l’avantage est que comparé à une stack classique, on ne se soucie absolument pas de l’infrastructure, des mises à jour, de la sécurité etc. En somme, Jamstack = Cloud.</p><p>Le terme Jamstack regroupe donc des choses bien différentes :</p><ul><li>un SSG qui prend du Markdown en entrée (ou texte brut ou HTML) et recrache un site en HTML (par exemple ce site),</li><li>une application full JS avec un framework tel que React qui appelle de nombreux services externes.</li></ul><p>Si je résume de manière assez prosaïque, la Jamstack, c’est du site statique qu’on saupoudre de Serverless au besoin. Frank de Jamstatic.fr, nous offre une <a href="https://jamstatic.fr/2019/02/07/c-est-quoi-la-jamstack/">synthèse en français</a> du site <a href="https://jamstack.wtf">Jamstack.wtf</a>.</p><p>Maintenant que vous savez vraiment en quoi consiste ces différentes technologies, voyons comment se frayer un chemin dans cette jungle technologique.</p><h2 id="Choisir-son-SSG"><a href="#Choisir-son-SSG" class="headerlink" title="Choisir son SSG"></a>Choisir son SSG</h2><p>Nous l’avons dit, il existe de très nombreux SSG. Le site <a href="https://jamstack.org/generators/">Jamstack</a> les regroupe presque tous, c’est donc un bon point de départ si vous n’avez pas déjà un SSG préféré.</p><p>De manière générale, il y a trois grandes méthodes pour générer le contenu. Cela dépend beaucoup du type de projet :</p><ul><li>Le volume de contenu est limité et il change peu (site vitrine…) :<ul><li>vous codez le HTML à la main,</li><li>vous utilisez un SSG minimaliste intégrant un moteur de template afin de placer les portions répétitives (header, footer, menu…) dans des templates séparés et de bénéficier de l’interpolation de variables (pour centraliser des données dans un fichier de config par exemple).</li></ul></li><li>Le volume de contenu est plus conséquent (blog, magazine, site d’entreprise, e-commerce…) et/ou il aura tendance à souvent évoluer (éditions, ajouts…), vous vous dirigez dans ce cas vers un SSG qui génère le HTML à partir de markup (Markdown en général, mais <a href="https://fr.wikipedia.org/wiki/Langage_de_balisage_l%C3%A9ger">d’autres existent</a>).</li><li>Lorsque l’architecture du contenu est très complexe ou de grande ampleur, il est possible d’utiliser des CMS Headless, lesquels délivrent le contenu via des API. On se dirigera alors vers un SSG capable de récupérer le contenu depuis une API ou vers un framework web tel que React, Angular ou Vue.js.</li></ul><p>Dans le premier cas, j’utilise <a href="https://github.com/Buzut/dopamine">Dopamine</a>. Il s’agit d’un SSG que j’ai créé, basé sur npm et <a href="https://ejs.co/">EJS</a>, un langage de template dont la syntaxe est celle de JavaScript. Il n’y a donc rien à réapprendre et on s’évite juste de faire des copier-coller dans tous les sens, parfaitement adapté pour les sites marketing.</p><figure><img src="/assets/landing-spotify.png" alt="homepage spotify"><figcaption>Page d'accueil de Spotify, focus sur le design</figcaption></figure><p>Dans un cas de figure comme celui-ci, on pourrait presque se contenter de coder le tout à la main sans SSG. Un générateur simple comme Dopamine offre un peu plus de confort avec du templating, l’inclusion d’un pré-processeurs CSS et la compilation du JavaScript.</p><p>Dans le second cas, en tant que développeur JavaScript, j’utilise <a href="https://hexo.io/">Hexo</a> (ce site l’utilise). C’est mon SSG de choix car il est développé en JavaScript, qu’il est rapide, très bien documenté et qu’il dispose d’un nombre de plugins et de thèmes impressionnants.</p><figure><img src="/assets/spotify-help.png" alt="homepage de spotify"><figcaption>Page d'aide de Spotify, le contenu est plus conséquent</figcaption></figure><p>On peut ici légitimement penser que le contenu n’est pas géré par les développeurs. Un SSG comme Hexo répondrait bien à cette problématique : tout est mis en place par les développeurs mais le contenu est totalement gérable sans toucher au code. Le contenu sera réparti entre des fichiers Markdown et Yaml qui pourront être édités sans connaissances particulières. Chaque fichier Markdown dispose d’ailleurs d’un en-tête Yaml afin de définir des métadonnées : de la meta description au nom de l’auteur, vous y mettez ce que vous voulez, le SSG pourra récupérer ces éléments pour les utiliser dans les templates.</p><p>La communauté d’Hexo est très développée en Asie (son principal développeur est Taïwanais). De ce fait, on verra plus souvent des articles sur Eleventy (s’écrit aussi 11ty) sur les sites et blogs francophones et anglophones. Néanmoins, toutes la documentation et l’aide dont on peut avoir besoin se trouve en anglais et l’offre de plugins est incomparablement plus riche du côté d’Hexo.</p><figure><img src="/assets/gant-eshop.png" alt="e-shop de gant"><figcaption>Page du e-shop de Gant, on imagine des produits, la gestion des réductions, informations de livraisons, lookbook…</figcaption></figure><p>Si le contenu n’est accessible qu’au travers d’API, on veillera à utiliser un SSG compatible avec cette méthode de récupération. Parmi les SSG JavaScript, à la fois <a href="https://ghost.org/docs/api/v3/hexo/">Hexo</a> et <a href="https://www.webstoemp.com/blog/headless-cms-graphql-api-eleventy/">11ty</a> en sont capables, mais on a également souvent recours à des SSG basés sur React et Vue.js : <a href="https://nextjs.org">Next.js</a>, <a href="https://www.gatsbyjs.com">Gatsby</a> et <a href="https://nuxtjs.org">Nuxt.js</a>.</p><p>Ces derniers SSG permettent bien entendu également de consommer du Markdown. On les préférera la plupart du temps pour des sites complexes et lorsque les sources de données sont multiples.</p><h2 id="Choisir-son-hebergeur"><a href="#Choisir-son-hebergeur" class="headerlink" title="Choisir son hébergeur"></a>Choisir son hébergeur</h2><p>Dans sa forme la plus simple, le site statique n’est qu’un ensemble de fichiers statiques. Virtuellement tous les hébergeurs conviendront donc. J’ai hébergé des sites statiques sur des mutualisés, des VPS, etc.</p><p>Un simple mutu fait donc l’affaire, lorsque le site est <em>build,</em> on peut le déployer de la manière qui nous plaît. J’ai par exemple déjà utilisé les modules <code>sftp</code> et <code>rsync</code> pour déployer un site respectivement sur un hébergement mutualité et un serveur virtuel.</p><p>Il existe cependant des hébergeurs spécialisés. Ces derniers permettront de se connecter directement au repo Git et s’occuperont automatiquement de build et deploy dès que des modifications seront effectuées sur une branche donnée. Ainsi, vous ne vous occupez plus que de pusher, le reste est automatique !</p><p>En outre, ces hébergeurs apportent un peu de dynamique aux sites statiques : gestion des formulaires, identification des utilisateurs (pour un espace membre par exemple), exécution de fonctions cloud…</p><p>Les deux principaux sont Vercel et Netlify. Leurs fonctionnalité sont assez similaires, chacun ayant toutefois ses domaines de prédilections. Netlify permet de facilement ajouter des fonctions aux sites statiques grâce à son catalogue de plugins. Vercel se distingue dans une fine gestion des fonctions cloud et permet le pre-rendering ; Vercel est développé par les auteurs de Next.js.</p><p>J’utilise pour ma part Netlify mais Vercel pourrait tout aussi bien convenir pour mes besoins. Pour plus d’informations à ce sujet, le mieux est de lire les retours de personnes ayant utilisé les deux : <a href="https://dev.to/maxniederman/netlify-vs-vercel-a-comparison-5643">Vercel vs Netlify</a> et retour d’expérience entre <a href="https://www.eliostruyf.com/netlify-vs-vercel-vs-azure-static-web-app/">Vercel, Netlify et Azure Static WebApp</a>.</p><p>Nous avons parlé de Git sans jamais mentionner les endroits où la plupart de nos dépôts sont stockés. GitHub et GitLab permettent tous les deux d’héberger des sites statiques : <a href="https://pages.github.com">GitHub Pages</a> et <a href="https://about.gitlab.com/stages-devops-lifecycle/pages/">GitLab Pages</a>. Comme ces deux platformes offrent des minutes de build dans tous leurs plans, il est possible de déclencher un build dès lors que de nouvelles modifications sont pushées.</p><figure><img src="/assets/gitlab-jobs.png" alt="les jobs gitlab"><figcaption>GitLab exécutant un processus de build</figcaption></figure><p>Les deux services offrent des fonctions spécifiques afin de pouvoir gérer les 404 et des redirections basiques. Pour plus de contrôle et de performances, il est même possible de <a href="https://tkainrad.dev/posts/using-hugo-gitlab-pages-and-cloudflare-to-create-and-run-this-website/">servir ces Git Pages depuis Cloudflare</a>.</p><p>Positionner Cloudflare en front permet un contrôle assez fin de la réécriture et redirections, options qui font notamment défaut à Netlify. Il n’est par exemple actuellement pas possible de forcer la redirection de tout <code>monsite.com/page/index.html</code> vers <code>monsite.com/page/</code> ni <code>monsite.com/test.html</code> vers <code>monsite.com/test</code>, ce que je trouve assez dommageable.</p><p>D’ailleurs, puisque l’on parle de Cloudflare, ce dernier aussi est monté dans <a href="https://workers.cloudflare.com">le train du Serverless et de la Jamstack</a>. Il propose des <em>Function as a Service (FaaS)</em>, ainsi qu’un hébergement spécifique pour les sites statiques. De quoi concurrencer Vercel et Netlify. Il s’adresse toutefois aux développeurs avec un plus grand besoin de contrôle (au détriment d’une complexité supérieure).</p><h3 id="Performances"><a href="#Performances" class="headerlink" title="Performances"></a>Performances</h3><p>En terme d’hébergement, il est important de mentionner les performances. Je peux facilement comparer car le présent site était il y a un an <a href="https://buzut.net/ciao-wordpress-hello-jamstack/">encore sur WordPress</a>.</p><p>Mon site WordPress était ultra-optimisé avec un thème codé à 100% par mes soins, de la même manière que le thème actuellement utilisé avec Hexo. De ce fait, les performances ne bénéficient pas d’un boost notable.</p><p>En revanche, j’ai essayé différents hébergements pour le site statique. Actuellement, ce site utilise Netlify. Cependant, la recherche est effectuée directement en front et les commentaires sont gérés par <a href="https://github.com/Buzut/jamments">Jamments</a> une API de commentaire open source dont je suis l’auteur. De ce fait, n’importe quel hébergeur ferait l’affaire.</p><figure><img src="/assets/jamstack-vps-hosting.png" alt="résultat Google Pagespeed pour un hébergement VPS"><figcaption>Performance avec un hébergement VPS OVH</figcaption></figure><p>J’ai tout d’abord utilisé un server virtuel sur lequel j’avais d’autres sites webs, dont des WordPress. Les fichiers étaient simplement servis par Apache, configuré aux petits oignons. On voit qu’on obtient de très bonnes performances avec cette configuration toute simple, mais efficace.</p><figure><img src="/assets/jamstack-gitlab-cloudflare-hosting.png" alt="résultat Google Pagespeed pour gitlab pages et cloudflare"><figcaption>Performance avec le site hébergé sur GitLab avec Cloudflare</figcaption></figure><p>Ensuite, j’ai voulu simplifier le workflow et ne plus du tout avoir à gérer de serveur : un VPS, comme un serveur dédié, nécessite une configuration et une maintenance manuelle, peut poser problème en cas de très fort trafic etc. J’ai donc naturellement utilisé GitLab pages (puisque c’est là que j’héberge le code du blog) et placé Cloudflare par dessus.</p><p>Notez qu’on obtient des performances raisonnables même sans Cloudflare, mais on bénéficie d’un peu moins d’options de configuration.</p><figure><img src="/assets/jamstack-netlify-hosting.png" alt="résultat Google Pagespeed pour netlify"><figcaption>Performance avec le site hébergé sur Netlify</figcaption></figure><p>En dernier lieu, le même site hébergé chez Netlify obtient, malgré tout le discours marketing, de moins bonnes performances. On obtient en échange une facilité de deploy inégalable et de nombreux plugins pour ajouter du dynamique à son site statique. De plus, Netlify permet de se connecter à de très nombreux outils pour gérer le back-office, c’est un gros plus.</p><h2 id="Choisir-son-back-office"><a href="#Choisir-son-back-office" class="headerlink" title="Choisir son back-office"></a>Choisir son back-office</h2><p>Lorsque l’on parle de site statique et de SSG, de manière classique, le contenu est géré au même niveau que le code : les fichiers sont côte à côte, tout s’édite via l’éditeur de code/texte et l’ensemble est versionné (en général dans Git).</p><p>Cette expérience peut convenir aux développeurs, mais s’il y a ou doit avoir dans l’équipe des rédacteurs non développeurs, cela peut vite devenir compliqué. Par ailleurs, force est de reconnaître que l’expérience que l’on a avec les CMS traditionnels me paraît plus plaisante de ce côté là.</p><p>Personnellement, lorsque je veux modifier une typo, je n’ai pas envie d’ouvrir mon éditeur de code, commiter ma modification, puis la pusher (ou build &amp; deploy selon la config du projet).</p><p>C’est là qu’entrent en jeux les CMS dit headless. Ce sont des interfaces d’éditions totalement découplées du front. Leur objectif est de gérer le contenu, rien d’autre. Contrairement aux CMS classiques, ils ne s’occupent pas de gérer le contenu.</p><p>Il y a principalement deux types de CMS headless :</p><ul><li>les CMS basés sur Git, on les utilise pour gérer des sites dont les sources sont principalement des fichiers texte (Markdown, Yaml, JSON),</li><li>les CMS <em>API driven</em>, permettent de gérer des sites de plus grande ampleur ou plus complexes.</li></ul><p>L’approche Git-based est tout à fait viable pour les sites d’envergures. À titre d’exemple, <a href="https://www.smashingmagazine.com/2020/01/migration-from-wordpress-to-jamstack/">Smashing Magazine</a> utilise Git et Netlify.</p><p>En revanche, un gros site e-commerce avec des milliers de références aura plutôt tendance à utiliser un CMS de la seconde catégorie. Ce choix est fortement corrélé au type de SSG précédemment abordé. Par ailleurs, le contenu issu d’un CMS API-based sera plus facilement consommé par différents terminaux (site, application mobile, montre connectée, assistant vocal, panneau d’affichage…).</p><p>Je ne vais pas m’éterniser sur les comparaisons entre les deux. Pour plus de détails, vous pouvez lire l’article de <a href="https://jamstatic.fr/2019/10/30/git-based-cms-vs-api-first-cms/">Jamstatic</a> à ce sujet.</p><h3 id="Git-based"><a href="#Git-based" class="headerlink" title="Git-based"></a>Git-based</h3><p>En ce qui me concerne, ce que je n’apprécie pas tellement avec l’approche Git-based, c’est d’avoir tous mes fichiers au même endroit (code et contenu). C’est un avantage pour certains, moi, ça m’ennuie un peu pour les projets dans lesquels il y a beaucoup de contenu et qu’il est modifié la plupart du temps indépendamment du code.</p><p>J’ai en effet pour habitude d’avoir des <a href="/cours/versioning-avec-git/bien-nommer-ses-commits.html">commits atomiques proprement nommés</a>. Lorsque l’on modifie principalement du contenu, cette règle devient plus compliquée à respecter. Ce n’est pas du tout le cas lorsqu’il s’agit d’un site marketing/vitrine où il y a moins de contenu et où celui-ci est intimement lié au design et donc au code.</p><figure><img src="/assets/gitlab-project.png" alt="résultat Google Pagespeed pour netlify"><figcaption>Il est tout à fait possible de créer un repo pour le code et un repo pour le contenu</figcaption></figure><p>Pour remédier à cette situation, je divise souvent le projet en deux repos : un pour le code à proprement parler et l’autre pour le contenu. Mis à part cela, c’est la configuration la plus simple et la plus portable. Votre contenu est dans un format standard flat file (Markdown, Yaml, JSON) et vous pouvez donc changer de CMS comme de SSG en un rien de temps.</p><p>Une grande partie des CMS Git-based se destinent à un SSG particulier. Il y en a cependant aussi des généralistes. Vous trouverez une liste assez complète sur le site <a href="https://jamstack.org/headless-cms/">Jamstack.org</a>. Nous allons nous concentrer sur les deux plus populaires parmi ceux prenant en charge tous les SSG.</p><h4 id="Netlify-CMS"><a href="#Netlify-CMS" class="headerlink" title="Netlify CMS"></a>Netlify CMS</h4><p><a href="https://www.netlifycms.org">Netlify CMS</a> est un outil open source (vous pouvez l’utiliser indépendamment de Netlify) qui offre des fonctions de CMS pour les projets hébergés sur GitHub, GitLab et Bitbucket).</p><p><video src="/assets/netlify-cms.mp4" autoplay controls loop></video></p><p>Il est possible de placer le code du CMS directement dans la codebase du projet. Ce CMS offre une interface simple et permet de gérer des projets relativement simple. Il permet toutefois de gérer un workflow de publication permettant une validation par un éditeur avant publication des contenus et permet également l’open authoring.</p><p>Néanmoins, Il ne convenait pas pour le présent site car il était impossible d’avoir plusieurs niveaux de répertoires : j’utilise un répertoire regroupant toutes <code>formations/</code> pour les formations, et chacune possède plusieurs chapitres regroupés dans un répertoire par formation. Cela me permet d’avoir des urls du genre <code>/formations/formation_y/chapitre_z/</code> et ça n’était pas possible dans Netlify CMS.</p><p>Si vous désirez en savoir plus sur la manière de le configurer, je vous invite à regarder cette <a href="https://youtu.be/_CNZJLYvINc">vidéo assez complète</a>.</p><h4 id="Forestry"><a href="#Forestry" class="headerlink" title="Forestry"></a>Forestry</h4><p>C’est vraiment la solution qui a retenu mon attention. Il s’agit d’un SaaS et non d’une solution open source. Vous ne vous souciez donc pas de l’hébergement, et vous devrez prendre un abonnement pour accéder à certaines fonctionalié.</p><p><video src="/assets/forestry.mp4" autoplay controls loop></video></p><p>Le tiers gratuit est cependant assez généreux et permet de bien tester la solution. Comme le CMS de Netlify, il permet de se connecter à GitHub, GitLab et Bitbucket et y ajoute aussi Azure DevOps. Les fonctionalités sont plus étendues que son cousin.</p><p>On aura notamment accès à l’édition avancée de répertoires contenant des sous-répertoires, des étideurs spéciaux pour des <em>data files</em> – ces fichiers JSON ou Yaml qui contiennent des données structurées consommées par le SSG (structure de menu, informations de contact, configuration de layout…).</p><p>Par ailleurs, on pourra procéder à la configuration directement depuis l’interface web sans avoir à s’égarer dans le fichier de config JSON.</p><h3 id="API-based"><a href="#API-based" class="headerlink" title="API-based"></a>API-based</h3><p>Contrairement aux CMS Git-based, ceux-ci sont destinés à un très large panel de solutions. Nous l’avons dit, le contenu accessible via une API peut tout aussi bien être consommé par un SSG que par une webapp, une application mobile ou desktop, un assitant vocal etc.</p><p>C’est l’un des principaux avantages de ce type de CMS. Par ailleurs, il est possible de requêter les données de manière dynamique depuis le site ou l’application et cela offre de nombreux avantages :</p><ul><li>génération de page selon la requête de l’utilisateur ou ses préférences (filtres…),</li><li>possibilité de faire de la recherche sur un volume de données important,</li><li>permet de ne pas avoir à relancer un build dès qu’un contenu est modifié (primordial dans les cas où le contenu change constamment).</li></ul><p>Le choix est immense ! Contrairement aux CMS Git-based, le choix de CMS API-driven est énorme. Même des CMS classiques comme Ghost, WordPress ou Drupal disposent d’API permettant d’en récupérer le contenu. Dans ce cas, le CMS ne gère que le contenu, il ne s’occupe pas de rendre et d’afficher les pages web et peut être considéré comme headless.</p><p>Par ailleurs, de nombreux services n’étant absolument pas destinés à être des CMS peuvent le devenir. C’est par exemple le cas de <a href="https://css-tricks.com/using-trello-as-a-super-simple-cms/">Trello</a> et <a href="https://www.freecodecamp.org/news/use-google-sheets-and-google-apps-script-to-build-a-blog-cms-c2eab3fb0b2b/">Google Sheets</a>.</p><p>Si un service propose une interface pour entrer des données et une API pour les récupérer, cela peut potentiellement devenir un CMS headless. Bien entendu, en plus des <em>hacks</em> précédemments mentionnés, il existe de nombreux services dédiés à cet usage.</p><p>Quoi qu’il en soit, avant d’adopter une solution API-driven, il faut bien être conscient de ses inconvénients :</p><ul><li>les possibilités offertes par l’API à votre disposition peuvent vous limiter (recherche fulltext, filtres avancés…),</li><li>les CMS et leurs API ont des limites d’usage : stockage, nombre de requêtes par min/heure/jour…</li><li>vendor lock-in : on retombe dans le même schéma qu’avec les CMS traditionnels, le contenu n’est pas facilement portable d’une solution à l’autre.</li></ul><p>Parmi les solutions les plus populaires, il se trouve les services cloud et les solutions open source à héberger soi-même. De nouveau, les deux côtés ont leurs avantages et leurs inconvénients. Une chose est certaine, héberger soit-même son CMS headless, qu’il s’agisse d’un WordPress ou d’une solution plus “moderne”, cela revient tout de même à retrouver les inconvénients de la gestion et de la maintenance backend classique.</p><p>Certains CMS sont plus spécialisés dans certains domaines que d’autres, mais de manière générale, vous définissez un modèle de donnée et les relations que ces données possèdent entre elles (un modèle <code>comments</code> et un modèle <code>articles</code>, ce dernier modèle peut référencer des <code>comments</code>). Une fois cela fait vous pouvez ajouter des données et les requêter.</p><p>Il va m’être difficile de vous présenter l’ensemble des possibilités du côté de l’offre API-driven. Le plus connu est incontestablement <a href="https://www.contentful.com">Contentful</a>, mais <a href="https://www.sanity.io">Sanity</a>, <a href="https://www.datocms.com">DatoCMS</a>, <a href="https://buttercms.com">ButterCMS</a> et <a href="https://prismic.io">Prismic</a> sont aussi populaires. Tous proposent une API GraphQL, certains proposent aussi REST.</p><p>Du côté des open source, le plus connu est sans conteste <a href="https://strapi.io">Strapi</a>, lequel offre du GraphQL ou REST au choix et supporte les principales bases SQL ainsi que SQLite et MongoDB. <a href="https://www.keystonejs.com">Keystone</a> – qui gagne en popularité – ne propose quant à lui que le GraphQL et supporte MongoDB et PostgreSQL. Strapi et Keystone sont codés en JavaScript.</p><p>Du côté de PHP, <a href="https://getcockpit.com">Cockpit</a> est assez populaire et semble offrir une interface élégante et efficace. Cockpit supporte SQLite et MongoDB et offre une API JSON REST.</p><p>Face à tant de choix, mieux vaut se diriger vers la section <a href="https://jamstack.org/headless-cms/">Headless CMS</a> de Jamstack.org et de voir celui qui correspond le mieux aux critères du projet.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>On réalise que les sites statiques ont de réels atouts et des usages bien définis. Les générateurs de sites permettent de s’affranchir des tâches manuelles et répétitives. De leur côté, les workflows modernes basés sur Git permettent de se concentrer sur le code et d’automatiser la génération et le déploiement des modifications.</p><p>Enfin, les APIs, lorsqu’elles sont appellées en front, permettent de suppléer aux déficits inhérents des sites statiques en leur adjoingant la puissance du cloud, quand bien même, techniquement parlant, il s’agit plus de cloud et de serverless que de site statique.</p><p>On notera également que les CMS traditionnels trouvent leur place dans cet environnement complexe : ils permettent d’une part de servir de headless CMS en récupérant le contenu de leur API, mais peuvent aussi être utilisés comme SSG.</p><p>WordPress possède plusieurs plugins à cet effet : <a href="https://github.com/leonstafford/static-html-output">Static HTML Output</a> et <a href="https://github.com/leonstafford/wp2static">WP2Static</a>. <a href="https://www.getshifter.io">Shifter</a> est un hébergeur spécialisé dans le WordPress statique : vous utilisez du WordPress, mais Shifter ne sert que du HTML statique et vous permet de paramétrer les règles de build de votre site.</p><p>Vous l’aurez compris, les possibilités sont infinies, l’ancien fait son renouveau et chaque besoin trouve sa solution !</p>]]></content>
    
    <summary type="html">
    
      Jamstack et générateurs de site statique sont devenus incontournables. Découvrons la raison de ce succès et les meilleurs solutions pour vos projets.
    
    </summary>
    
    
    
      <category term="JavaScript" scheme="https://buzut.net/tag/JavaScript/"/>
    
      <category term="Outils" scheme="https://buzut.net/tag/Outils/"/>
    
  </entry>
  
  <entry>
    <title>10 règles d&#39;UX design spécifiques au e-commerce</title>
    <link href="https://buzut.net/10-regles-d-ux-design-specifiques-au-e-commerce/"/>
    <id>https://buzut.net/10-regles-d-ux-design-specifiques-au-e-commerce/</id>
    <published>2020-05-03T22:00:00.000Z</published>
    <updated>2024-09-05T06:56:59.832Z</updated>
    
    <content type="html"><![CDATA[<p>L’UX design (design de l’expérience utilisateur) est depuis quelques années dans toutes les bouches, le terme est à la mode. En contrepartie, nombre de sites ont encore beaucoup de progrès à faire. Tout site tire profit d’une bonne UX, cependant il y a quelques règles qui sont spécifiques au e-commerce.</p><p>Quand on connait l’impact spectaculaire qu’elles peuvent engendrer sur le taux de conversion et donc le chiffre d’affaire, mieux vaut ne pas passer à côté ! Voyons donc les quelques règles à absolument avoir en tête lors du design et du développement d’un site e-commerce. Il s’agit de conseils concrets, cet article est donc aussi bien destiné aux designers qu’aux développeurs, let’s go!</p><span id="more"></span><p>Je suis un développeur et je n’ai pas de compétence particulière en design d’interface (UI design). Cependant, j’estime qu’il est important d’acquérir une compréhension du fonctionnement d’un design. En outre, co-construire avec le designer (ou l’équipe design) sur les projets que je réalise mène toujours à de meilleurs résultats.</p><p>En ce sens, lors de la refonte du site d’<a href="https://www.alphapole.com/">Alphapole</a>, j’ai effectué de nombreuses recherches et analysé les différentes itérations des designs, afin d’identifier les points de friction potentiels au niveau de l’expérience utilisateur.</p><h2 id="Ajout-au-panier"><a href="#Ajout-au-panier" class="headerlink" title="Ajout au panier"></a>Ajout au panier</h2><p>Le bouton d’ajout au panier est omniprésent sur un site e-commerce. C’est l’essence même d’un site e-commerce que de pouvoir ajouter les articles dans le panier.</p><p>Je ne vais donc pas parler ici de la forme et du positionnement de ce bouton, lequel est un <abbr title="click to action">CTA</abbr> qui répond aux règles d’UX classiques du call to action. Je vais plutôt détailler le comportement attendu lors du clic sur ce bouton.</p><p>En effet, l’ajout au panier est une étape essentielle dans le processus d’achat, il ne faut donc ni perdre ni frustrer l’utilisateur. Qu’attend-il du site lorsqu’il clique sur ce bouton ?</p><ul><li>Il veut évidemment que le bouton ajoute l’article au panier,</li><li>Il veut être certain que l’article ait bien été ajouté,</li><li>Il veut ensuite poursuivre ses achats ou procéder au règlement.</li></ul><p><video class="responsive" src="/assets/alphapole-add-to-cart.mp4" autoplay loop></video></p><p>Notre e-shopper veut être certain que son action a bien été prise en compte. Il faut donc que <a href="https://www.nngroup.com/articles/cart-feedback/">l’interface reflète l’action effectuée [en]</a>, un feedback visuel. Sans cela, il risque de cliquer une seconde fois sur le bouton et se retrouver avec l’article en double ou triple dans son panier.</p><p>En général, un site e-commerce possède une icône “panier”. Il est donc conseillé d’avoir un badge qui indique le nombre de produits présents dans le panier et optionellement le total ou sous-total. Ainsi, un ajout au panier viendra immédiatement mettre à jour ces informations. Cependant, cette icône manque de visibilité et, même avec une subtile animation, il y a de fortes chances que l’utilisateur ne le remarque pas.</p><p>Pour plus de visibilité, deux options sont communément adoptées. La première est d’emmener l’utilisateur directement sur la page panier. Cette première option est à considérer très soigneusement. En effet, “<strong>ajouter au panier</strong>“ ne veut pas dire “<strong>aller au panier</strong>“. À moins que le site privilégie des paniers avec un seul article, cette option laisse moins de chance à l’utilisateur d’augmenter son panier moyen.</p><p>Dans un restaurant, lorsque vous avez fini votre plat, le serveur s’enquiert de savoir si vous voulez un dessert ou des cafés avant de vous apporter l’addition. La logique est ici la même. À moins qu’il s’agisse d’un site ne vendant qu’un seul type de produit ou des produits mutuellement exlusifs, cette solution est à éviter si vous voulez augmenter le panier moyen.</p><p>L’autre option existe sous différentes formes. Certains sites mènent à une page intermédiaire, d’autres utilisent une modale (ou popin). La logique est la même, on confirme à l’utilisateur que son article a bien été ajouté au panier. L’utilisateur veut également être certain que <strong>le bon article</strong> a été ajouté au panier. On affiche donc une image de l’article une image et le nom de l’article et la quantité ajoutée.</p><figure><img src="/assets/patagonia-add-to-cart.jpg" alt="ajout au panier chez Patagonia"><figcaption>Chez Patagonia, on a une fenêtre qui descend, impossible pour l'utilisateur de ne pas le voir. Seul problème, elle disparaît d'elle même après quelques secondes. Mieux vaut laisser l'utilisateur maître de l'action</figcaption></figure><p>On peut également en profiter pour présenter à l’utilisateur un résumé de sa commande avec le nombre total d’articles dans le panier ainsi que le sous-total. De plus, on peut en profiter pour faire un cross-selling ou de l’upselling. Nous allons y revenir.</p><p>J’ai tendance à préférer l’utilisation de la modale à la page intermédiaire. Ainsi, le bouton fait vraiment ce qu’il indique : il se contente d’ajouter au panier et laisse l’utilisateur sur la même page. Vous avez dès lors l’opportunité de lui présenter ses options :</p><ul><li>Une liste de produits qui pourraient l’intéresser en complément (cross-selling) ou remplacement (upselling),</li><li>Se rendre sur le panier si c’est ce qu’il souhaite,</li><li>Poursuivre ses achats (on ferme simplement la modale et l’utilisateur poursuit sa navigation comme il l’entend.</li></ul><h2 id="Upselling-et-cross-selling"><a href="#Upselling-et-cross-selling" class="headerlink" title="Upselling et cross-selling"></a>Upselling et cross-selling</h2><p>Ces deux pratiquent signifient respectivement vendre des produits complémentaires et vendre un produit plus haut de gamme. Les deux peuvent s’utiliser en complément, tout dépend du besoin de la boutique en question.</p><p>L’important est de saisir les opportunités de x-selling et d’upselling. Certaines boutiques présentent ces options sur la page panier. La règle n’est ici pas absolue, mais une fois l’utilisateur sur la page panier, mieux vaut ne plus trop le distraire.</p><p>En revanche, les pages produits et la modale de confirmation d’ajout au panier sont des emplacements tout indiqués. Sur la page produit, le prospect exprime un besoin ou une envie par rapport au produit en question. Il a donc de grande chance d’être réceptif à des produits complémentaires de la même gamme et/ou des produits plus haut de gamme répondants au même besoin.</p><figure><img src="/assets/cross-selling-ovh.png" alt="OVH vous propose d'autres produits lorsque vous achetez un nom de domaine"><figcaption>OVH propose toujours des produits complémentaires lors d'une commande</figcaption></figure><p>Lorsque notre utilisateur ajoute un produit au panier, il indique clairement si ce n’est une volonté d’acquérir ce produit, un très fort intérêt. C’est le moment idéal pour lui proposer des produits qui se marient bien avec celui qu’il est sur le point d’acquérir. Nouveau téléphone ou ordinateur ? Une housse ou coque de protection est une très bonne idée. Nom de domaine ? Voici une offre d’hébergement idéale pour votre prochain site…</p><p>Vous saisissez l’idée. L’utilisateur se sent accompagné dans sa démarche d’achat par un site “qui le comprend” et vous augmentez à coup sûr le panier moyen. C’est gagnant-gagnant.</p><h2 id="Page-panier"><a href="#Page-panier" class="headerlink" title="Page panier"></a>Page panier</h2><p>La page panier est un incontournable. La première erreur est de ne pas en avoir. Certains sites se limitent en effet à un “mini panier” qui s’affiche au survol de l’icône panier ou sur le côté. Ce choix a pour principal inconvénient de ne pas présenter une vue globale des éléments du panier ainsi qu’une interaction facile.</p><p>Par ailleurs, les internautes <a href="https://www.nngroup.com/articles/shopping-cart/">ont certaines attentes [en]</a> concernant cette page :</p><ul><li>articles avec les images,</li><li>pouvoir retourner sur la fiche produit d’un clic,</li><li>pouvoir facilement ajuster la quantité.</li></ul><p>Les utilisateurs utilisent aussi le panier comme moyen de stockage. Ils y reviennent par la suite pour comparer différents produits entre lesquels ils hésitaient. Il faut donc leur faciliter la tâche en affichant les différentes informations utiles et un lien vers la fiche produit complète (en général le titre et la photo du produit). Cela permet de facilement retrouver tous les détails d’un produit avant la décision finale.</p><p>En outre, la panier doit permettre de supprimer des articles et d’ajuster la quantité facilement avec une mise a jour automatique. N’obligez pas l’utilisateur à cliquer sur un bouton pour calculer le nouveau sous-total et prendre en compte ses changements.</p><figure><img src="/assets/panier-ralph-lauren.png" alt="panier site Ralph Lauren"><figcaption>Ralph Lauren permet d'un clic de modifier, supprimer ou sauvegarder les articles du panier</figcaption></figure><p>Une fonction qui peut s’avérer appréciable : “envoyer ce panier par email”. De nombreux shoppers commencent sur mobile et terminent le processus d’achat sur leur ordinateur. Vous leur facilitez ainsi la tâche et vous gagnez une adresse email. Mieux vaut un tiens que deux tu l’auras ! De cette manière, vous serez en mesure de relancer le client s’il ne termine pas son achat, encore une fois, gagant-gagnant. On peut alternativement offrir la sauvegarde via un compte client.</p><p>Par ailleurs, c’est généralement sur cette page que se trouve le champ permettant d’entrer un code de réduction. Il est conseillé de ne pas rendre ce champ trop proéminent. L’utilisateur qui n’a pas de réduction risque d’être frustré de savoir qu’il est possible d’obtenir des réduction mais que lui n’en a pas.</p><p>En revanche, l’utilisateur qui possède un code de réduction cherchera l’endroit où rentrer son code. Au lieu de directement afficher le champ avec le bouton qui permet d’appliquer le code, vous pouvez simplement utiliser un lien “J’ai un code de réduction”. Ce n’est qu’au clic qu’apparaît le champ permettant à l’utilisateur d’entrer son code.</p><h3 id="Mini-panier"><a href="#Mini-panier" class="headerlink" title="Mini panier"></a>Mini panier</h3><p>Il ne faut surtout pas penser que le mini panier est à bannir. Ce qui est contre-productif est de le concevoir en tant que substitut au panier normal. En revanche, grâce au mini panier, l’utilisateur peut d’un coup d’œil savoir ce que contient son panier et quel est le total de la commande.</p><p><video class="responsive" src="/assets/alphapole-mini-cart.mp4" autoplay loop></video></p><p>Évidemment, le mini panier permet de naviguer vers la panier complet. En outre, l’utilisateur apprécie d’avoir l’option de passer directement au règlement de la commande. Comme toujours, l’utilisateur est maître et s’il le souhaite, il gagne un clic en évitant l’étape intermédiaire de la page panier.</p><p>En général, le mini panier apparaît au survol de l’icône du panier. Chez Alphapole, pour la version mobile, j’ai fait le choix de ne pas avoir de mini panier car son utilisabilité est bien moindre sur petit écran.</p><h2 id="Le-checkout"><a href="#Le-checkout" class="headerlink" title="Le checkout"></a>Le checkout</h2><p>C’est là que tout se passe, l’étape finale jusqu’au paiement ! Cette étape est souvent appelée “tunnel de conversion”, car il regroupe les informations de livraison, facturation et paiement.</p><p>J’enfonce sûrement une porte ouverte mais il est bon de le rappeler : moins il y a de choses à rentrer pour l’utilisateur, meilleur sera le taux de conversion. Cela passe par de nombreuses choses :</p><ul><li>Répartition des champs du formulaire en étapes ou one-page,</li><li>Mettre en avant les éléments de réassurance,</li><li>Enlever toutes les distractions pour que l’utilisateur se concentre sur sa mission.</li></ul><p>La première chose à faire est de définir les étapes du processus de checkout. Il est tentant de tout mettre sur une seule page, cependant, s’il y a trop de champs, cela risque d’effrayer l’utilisateur.</p><p>Par ailleurs, si vous vendez des services numériques par exemple, vous pouvez peut-être vous contenter du nom, prénom et informations de paiement. Dans ce cas, le one-page checkout peut faire sens. Il n’y a là pas de règle unique, le mieux est de trouver <a href="https://home.bluesnap.com/snap-center/blog/4-payment-page-design-examples-to-copy/">des exemples</a> qui peuvent s’appliquer à votre situation.</p><p>Voyez par exemple dans <a href="https://black.bird.eu/fr/blog/etude-de-cas-optimisation-checkout-bananamoon.html">cet article</a> pourquoi Banana Moon a fait le choix de migrer d’un checkout en une seule page vers un checkout en plusieurs étapes. Quel que soit votre choix, tentez de limitez au maximum les informations et de diminuez le travail de votre client.</p><p>Par exemple, si vous demandez les informations de facturation, il y a de grandes chances pour qu’elles soient les mêmes que celles de livraisons. Vous pouvez n’affichez ce formulaire que dans le cas où l’utilisateur clique sur “entrer une adresse de facturation différente”.</p><p>En outre, comme mentionné, il ne faut pas que l’utilisateur soit distrait. On peut enlever tous les éléments d’interaction tels que les menus et autres éléments cliquable. Amazon adopte par exemple cette approche. Vous ne laissez dans ce cas qu’un lien permettant à l’utilisateur de revenir au panier s’il le souhaite.</p><figure><img src="/assets/amazon-checkout.png" alt="page de confirmation de commande Amazon"><figcaption>Sur Amazon, au stade de la confirmation de paiement, le menu et la plupart des liens disparaissent</figcaption></figure><p>De plus, faites en sorte que le résumé de la commande soit facilement localisable, même sur mobile, l’utilisateur voudra sûrement vérifier une dernière fois le total, les frais de port et le contenu de sa commande avant de la payer.</p><p>En dernier lieu, vous avez sûrement déjà vécu cette expérience où, prêt à réaliser votre achat, on vous demande de créer un compte. Mais vous souhaitez juste régler la commande, pas prendre une carte d’abonnement. C’est un peu comme si au supermarché, vous étiez obligé de prendre une carte de fidélité pour pouvoir acheter quoi que ce soit.</p><p>Obliger l’utilisateur a avoir un compte peut faire sens pour Amazon, cependant, c’est un frein énorme pour la majorité des sites, même de marques connues. Vous devez donc impérativement offrir un <i>guest checkout</i> qui ne nécessite pas de compte. Une fois la commande passée, comme vous avez déjà toutes les informations nécessaires, vous pouvez proposer à l’utilisateur de créer un compte (en lui expliquant les avantages, tel que le suivi de commande). Il n’a alors plus qu’à entrer un mot de passe et voilà le compte est créé !</p><h3 id="Aspects-techniques"><a href="#Aspects-techniques" class="headerlink" title="Aspects techniques"></a>Aspects techniques</h3><p>Pour faciliter la saisie des informations, on veillera bien à préciser le type adéquat pour chaque champ de formulaire. Ainsi, le clavier du téléphone s’adapte lorsqu’il faut saisir un téléphone ou un email par exemple. C’est bien plus agréable !</p><p>En outre, si rentrer des informations est fastidieux, ce n’en est pas moins inévitable… ou presque ! Que l’on soit sur mobile ou desktop, la plupart des navigateurs connaissent déjà les informations de l’utilisateur et peuvent donc remplir automatiquement le formulaire. <figure><img src="/assets/pre-remplissage-formulaire.jpg" alt="pre-remplissage du formulaire avec iOS"><figcaption>Safari sur iOS propose de pré-remplir le formulaire sur le checkout d’Alphapole, l’utilisateur n’a alors rien à saisir manuellement</figcaption></figure></p><p>Le pre-remplissage fonctionne pour toutes les coordonnées ainsi que pour les cartes de crédits. Il suffit de bien indiquer au navigateur quelles sont les données à placer dans chaque champ. Cela s’effectue via des <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Attributs/autocomplete">attributs HTML</a>.</p><p>Enfin, la dernière chose à faire est de penser à désactiver la correction automatique sur les champs qui risquent d’être problématique. Ainsi, personne n’a envie de se voir corriger l’orthographe de son nom de famille… De nouveau, la désactivation de la correction se fait via un simple attribut HTML sur les <code>input</code> et <code>textarea</code> :</p><pre><code>&lt;input autocorrect=&quot;off&quot; autocapitalize=&quot;off&quot; spellcheck=&quot;false&quot;&gt;&lt;textarea autocorrect=&quot;off&quot; autocapitalize=&quot;off&quot; spellcheck=&quot;false&quot;&gt;&lt;/textarea&gt;</code></pre><h4 id="Informations-de-paiements"><a href="#Informations-de-paiements" class="headerlink" title="Informations de paiements"></a>Informations de paiements</h4><p>Entrer ses numéros de carte n’est à nouveau pas le plus fun dans une transaction, d’autant plus que c’est souvent source d’erreur. Une fois de plus, les navigateurs tentent de faciliter la vie des internautes.</p><p>Si les informations de carte sont déjà disponibles dans le navigateur, elles pourront être remplies automatiquement à la demande de l’utilisateur. Si elles ne sont pas présentes, certains navigateurs/appareils offriront la possibilité de scanner les numéros de la carte via l’appareil photo.</p><p>Ces deux fonctionnalité ne sont disponibles que lorsque les attributs des champs de carte sont <a href="https://stackoverflow.com/a/25925195/1717735">bien renseignés</a>.</p><p>En terme de design, si vous en avez le contrôle, rendre le tout visuel améliore grandement  l’expérience utilisateur.</p><figure><img src="/assets/carte-bancaire.png" alt="formulaire de carte avec visualiseur "><figcaption>Il est ici indéniable que la projection des informations sur un visuel de carte améliore l'UX</figcaption></figure><h2 id="Programme-fidelite"><a href="#Programme-fidelite" class="headerlink" title="Programme fidélité"></a>Programme fidélité</h2><p>Pour certains, cela peut paraître anodin, mais les consommateurs adorent les programmes de fidélité. Vous seriez surpris de savoir combien de clients demandent si un tel programme existe.</p><p>Cela créé véritablement un lien entre un client et l’entreprise. Un programme fidélité, comme son nom l’indique, permet bien entendu de “récompenser” la fidélité des clients : ventes privées, offres spéciales, réductions en fonction du statut.</p><figure><img src="/assets/programme-fidelite-alphapole.jpg" alt="programme de fidélité Alphapole"><figcaption>Le programme fidélité Alphapole à base de points</figcaption></figure><p>Le programme de fidélité est aussi l’occasion de créer une actualité avec les clients. Vous les contactez ainsi pour leur signifier qu’ils ont gagné des points, qu’ils ont débloqué une nouvelle réduction ou un statut de membre. Grâce à cela, ils pensent plus souvent à l’entreprise et saisiront peut être l’occasion pour réaliser un achat.</p><h2 id="Le-compte-client"><a href="#Le-compte-client" class="headerlink" title="Le compte client"></a>Le compte client</h2><p>Un site e-commerce peut difficilement se passer de compte client. Ces derniers voudront pouvoir vérifier le statut d’une commande, télécharger leur facture, enregistrer leurs coordonnées et préférences (livraison, facturation, cartes bancaires…) pour faciliter les prochains achats etc.</p><p><video src="/assets/woocommerce-mon-compte.mp4" autoplay loop></video></p><p>De même que pour le checkout, l’espace client est parfois contraint par la solution technique utilisée (Prestashop, WooCommerce, Shopify…). Il ne sera donc pas toujours possible, ou techniquement compliqué, donc financièrement assez cher, de fournir une interface 100% personnalisée.</p><p>Bien qu’il soit presque toujours possible d’offrir une expérience unique aux visiteurs, dans ce domaine, on peut s’en tenir à un design simple et fonctionnel :</p><ul><li>espace facilement accessible depuis le menu,</li><li>facilité d’accès aux informations et modifications aisée lorsqu’utile,</li><li>accès aux commandes en cours et passées (avec téléchargement des factures),</li><li>informations sur le programme fidélité si applicable,</li><li>récupération du mot de passe (social login et magic link connect sont un plus).</li></ul><p>Parmi les sites desquels je suis client, deux m’ont particulièrement impressionnés par leur design fonctionnel :</p><ul><li>Lacoste vous accueille immédiatement dans “son club” en mettant en avant son programme fidélité. Le <a href="https://dribbble.com/shots/11344878-compte-client-lacoste">compte client</a> est simple et épuré, tout en étant design.</li><li>SEOPress (outil de référencement) <a href="https://dribbble.com/shots/11168239-SEOPress-customer-account">guide les clients</a> grâce à la mise en avant de guides et d’éléments essentiels au démarrage de l’outil. Les autres options sont toujours à portée de main.</li></ul><h2 id="2FA"><a href="#2FA" class="headerlink" title="2FA"></a>2FA</h2><p>Si ce terme ne vous parle pas, il s’agit d’un acronyme signifiant <em>Two-factor authentication</em>, soit en français, l’authentification à deux facteurs.</p><p>Cela peut concerner entre autre le compte client. Il s’agit en fait de confirmer l’identité de la personne par une seconde méthode (la première étant en général le mot de passe). Cette seconde méthode passe en général par un de ces trois moyens :</p><ul><li>envoi d’un code ou d’un lien de validation sur l’email préalablement connu de l’utilisateur,</li><li>envoi d’un code par SMS ou appel automatisé sur le numéro de téléphone préalablement connu de l’utilisateur,</li><li>validation depuis une application préalablement installée sur l’appareil de l’utilisateur.</li></ul><p>L’email comme le SMS sont éligibles à l’autofill. Par exemple, sur macOS et iOS, si vous recevez un SMS contenant un jeton de sécurité et que vous êtes sur la page vous demandant de rentrer ledit jeton, Safari vous pourra vous proposer de remplir le jeton automatiquement, sans même que vous ayez eu à consulter vos messages.</p><p>Pour cela, comme vous l’autofill des coordonnées et des informations de carte bancaire, il faut renseigner un attribut spécifique. Il n’y en a qu’un seul.</p><pre><code>  autocomplete=&quot;one-time-code&quot;</code></pre><p>Voilà, il me semble que nous avons fait le tour. N’hésitez pas à partager d’autres règles que j’aurais oublié. De plus, si vous avez des retours d’expérience sur le sujet, faites en part dans les commentaires, c’est toujours très intéressant.</p>]]></content>
    
    <summary type="html">
    
      En e-commerce, l&#39;UX design est capital car il a un impact direct sur le taux de conversion de la boutique. Découvrons les règles d&#39;UX indispensables.
    
    </summary>
    
    
    
      <category term="HTML/CSS" scheme="https://buzut.net/tag/HTML-CSS/"/>
    
      <category term="Design" scheme="https://buzut.net/tag/Design/"/>
    
      <category term="Marketing" scheme="https://buzut.net/tag/Marketing/"/>
    
  </entry>
  
  <entry>
    <title>Boucles JavaScript : maîtrisez les toutes</title>
    <link href="https://buzut.net/maitrisez-les-differents-types-de-boucles-javascript/"/>
    <id>https://buzut.net/maitrisez-les-differents-types-de-boucles-javascript/</id>
    <published>2019-12-16T23:28:02.000Z</published>
    <updated>2024-09-05T06:56:59.840Z</updated>
    
    <content type="html"><![CDATA[<p>Le JavaScript dispose d’une demi-dizaine d’instructions distinctes permettant d’effectuer une boucle sur une variable. Quelles sont les méthodes les plus adaptées aux différents types de valeurs ? Quelles sont les implications de telle ou telle méthode ? Dans quel contexte préférer une instruction plutôt qu’une autre ? Autant de questions auxquelles nous allons apporter une réponse.</p><span id="more"></span><p>Le JavaScript comporte de très nombreuses instructions permettant d’effectuer une boucle sur une valeur. Cependant, si nous considérons seulement celles dont c’est l’objet premier – en omettant celles dont la boucle est inhérente à leur fonctionnement (<code>.map</code>, <code>.filter</code>…) – nous en dénombrons exactement cinq :</p><ul><li><code>for</code>,</li><li><code>for of</code>,</li><li><code>for in</code>,</li><li><code>while</code>,</li><li><code>do while</code>.</li></ul><p>À cela s’ajoute une méthode dont disposent de nombreux itérables : <code>.forEach</code>. Voyons donc quelles sont les différences fondamentales entre ces différentes instructions.</p><h2 id="while-le-basique"><a href="#while-le-basique" class="headerlink" title="while, le basique"></a><code>while</code>, le basique</h2><p>Le <code>while</code> est l’instructions de boucle la plus simple que l’on retrouve en JavaScript. Cette instruction ne prend qu’un seul argument : une condition. Tant que la condition retourne <code>true</code>, la boucle se poursuit.</p><p>Toute variable à initialiser, valeur à actualiser, test à effectuer (hors celui de la condition) doivent s’effectuer avant ou dans le corps de la boucle.</p><pre><code class="js">const countUntil = 10;let i = 0;while (i &lt;= countUntil) &#123;    console.log(i++); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10&#125;</code></pre><p>Étant donné que la condition attendue est une expression, on peut tout à fait y placer une expression qui modifie notre structure de contrôle, comme incrémenter notre variable par exemple.</p><pre><code class="js">const countUntil = 10;let i = 0;while (i++ &lt; countUntil) &#123;    console.log(i); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10&#125;</code></pre><p>Vous avez peut être remarqué que la comparaison est passé d’un <em>inférieur ou égal</em> à un <em>exclusivement inférieur à</em>. En effet, la condition est exécutée <strong>avant</strong> chaque tour de boucle. C’est d’ailleurs pour cela que le premier exemple compte à partir de zéro tandis que le second commence à un.</p><p>De ce fait, la première fois que la condition est évaluée, <code>i</code> vaut 0, il est incrémenté et le corps de la boucle est exécuté. Lors de la dernière exécution, <code>i</code> vaut 9, est incrémenté et la boucle s’exécute. Enfin, la condition est de nouveau exécutée mais <code>i</code> vaut cette fois 10 donc la boucle s’arrête là.</p><h2 id="for-le-classique"><a href="#for-le-classique" class="headerlink" title="for, le classique"></a><code>for</code>, le classique</h2><p><code>for</code> est la boucle classique telle qu’on la trouve dans l’ensemble des langages issus de la famille du C. Elle prend en paramètres trois expressions optionnelles, séparées par des points virgules :</p><ul><li>initialisation : valeur à initialiser avant le démarrage de la boucle,</li><li>condition : condition qui détermine si la boucle continue ou s’arrête, elle est évaluée avant chaque tour de boucle,</li><li>expression finale : expression évaluée après chaque tour de boucle.</li></ul><pre><code class="js">const test = 5;for (let i = 0; i &lt; test; i++) &#123;    console.log(i); // 0, 1, 2, 3, 4&#125;</code></pre><p>Étant donné que chacun des arguments est optionnel, on peut par exemple décider de n’utiliser que la condition, ce sera alors sur une variable définit en amont ; ou alors ne pas utiliser l’expression… Bref, tout est possible.</p><p>Il suffit pour cela d’utiliser l’instruction vide (<i>empty statement</i>). Cette dernière permet simplement de <em>remplir la case</em> dans laquelle JavaScript attend une réponse. Par exemple, dans le cas où l’on voudrait ignorer le paramètre d’initialisation.</p><pre><code class="js">const test = 5;let i = 0;for (; i &lt; test; i++) &#123;    console.log(i); // 0, 1, 2, 3, 4&#125;</code></pre><p>Le résultat est exactement le même que précédemment, même si l’exemple est tout à fait idiot, c’est possible. Par ailleurs, comme chacun de ces arguments doit être une expression – c’est à dire un élément qui retourne une valeur – on peut en tirer parti pour mettre en place un contrôle plus riche.</p><p>Par exemple, vous lirez (ou avez lu) qu’il est conseillé pour des raisons de performances de pré-calculer la longueur d’un array dans le paramètre d’initialisation plutôt que de le recalculer à chaque tour de boucle.</p><pre><code class="js">const test = [1, 2, 3, 4, 5];// ainsi, il vaut mieux remplacer celafor (let i = 0; i &lt; test.length; i++) &#123;    console.log(i); // 0, 1, 2, 3, 4&#125;// par celafor (let i = 0, length = test.length; i &lt; length; i++) &#123;    console.log(i); // 0, 1, 2, 3, 4&#125;</code></pre><p>Au delà de l’exemple, cette optimisation n’est plus utile dans la plupart des cas. En effet, presque tous les moteurs JavaScript possèdent un compilateur JIT (<i>just in time</i> ou compilation à la volée), lequel sait optimiser ce genre de cas tout seul.</p><h2 id="do-while-la-tete-a-l’envers"><a href="#do-while-la-tete-a-l’envers" class="headerlink" title="do...while, la tête à l’envers"></a><code>do...while</code>, la tête à l’envers</h2><p>Celle-ci est la petite sœur de la boucle <code>while</code>, son fonctionnement est sensiblement le même mais l’évaluation de la condition est inversée. La boucle s’exécute puis évalue la condition.</p><p>Si cette dernière retourne <code>true</code> alors la boucle continue, sinon, elle s’arrête. Si on transpose les exemples du <code>while</code>, le premier exemple donne le même résultat, mais dans le second, la boucle s’exécutera une fois de plus.</p><pre><code class="js">const countUntil = 10;let i = 0;do &#123;    console.log(i++); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10&#125; while (i &lt;= countUntil);</code></pre><p>Ici, <code>i</code> est incrémenté dans le corps de la boucle, cela ne change rien au résultat.</p><pre><code class="js">const countUntil = 10;let i = 0;do &#123;    console.log(i); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10&#125; while (i++ &lt; countUntil);</code></pre><p>En revanche, lorsque l’incrément se fait directement dans la condition, on part de zéro et on compte jusqu’à dix, alors même que le condition est un <em>inférieur exclusif</em>.</p><p>En effet, la boucle s’exécute <strong>avant</strong> d’évaluer la condition. Ainsi, lors du premier tour, <code>i</code> est à 0, la boucle poursuit jusqu’à ce que <code>i</code> soit à 10, alors la condition est de nouveau évaluée, elle est toujours à <code>true</code> car <code>i</code> peut-être <em>inférieur ou égal</em> à 10, donc <code>i</code> est incrémenté, passe à 11 et une nouvelle exécution de boucle est validée. <code>i</code> valant maintenant 11, la prochaine condition stoppe la boucle.</p><h2 id="for-in-et-for-of-pour-les-objets"><a href="#for-in-et-for-of-pour-les-objets" class="headerlink" title="for..in et for..of pour les objets"></a><code>for..in</code> et <code>for..of</code> pour les objets</h2><p>La boucle <code>for..in</code> permet de <em>looper</em> sur les propriétés énumérables et non <code>Symbol</code> d’un objet.</p><pre><code class="js">const person = &#123;    firstname: &#39;Mickey&#39;,    lastname: &#39;Mouse&#39;,    nickname: &#39;Mick&#39;&#125;;for (const propt in person) &#123;    console.log(propt); // &quot;firstname&quot;, &quot;lastname&quot;, &quot;nickname&quot;    console.log(person[propt]); // &quot;Mickey&quot;, &quot;Mouse&quot;, &quot;Mick&quot;&#125;</code></pre><p>Sa cousine, la boucle <code>for..of</code> est plus récente car il s’agit d’une addition au langage à partir de l’ES2015. Cette dernière <em>loop</em> sur les valeurs d’un itérable (objet, tableau, <code>Set</code>, <code>Map</code>, <code>NodeList</code>…).</p><pre><code class="js">const person = &#123;    firstname: &#39;Mickey&#39;,    lastname: &#39;Mouse&#39;,    nickname: &#39;Mick&#39;&#125;;for (const val of person) &#123;    console.log(val); // &quot;Mickey&quot;, &quot;Mouse&quot;, &quot;Mick&quot;&#125;</code></pre><p>Il est même possible d’itérer sur un <code>String</code>, cela se fera sur chacune des lettres du mot. Cette structure peut aussi s’avérer très utile pou itérer sur des <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/TypedArray"><code>TypedArray</code></a>.</p><p>Bien que ces deux instructions semblent très similaires, certaintes différences d’envergure existent :</p><ul><li><code>for..in</code> liste les clefs tandis que <code>for..of</code> liste les valeurs,</li><li><code>for..in</code> et <code>for..of</code> n’itèrent pas sur les mêmes choses. Tandis que <code>for..in</code> boucle sur toutes les propriétés énumérables autre que <code>Symbol</code>, <code>for..of</code> parcourt l’ensemble des données contenues dans l’objet <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/iterateurs_et_generateurs#It%C3%A9rables">itérable</a>.</li></ul><p>Cette seconde différence mène à des résultats parfois non prévus par le développeur.</p><pre><code class="js">const testLoop = [1, 2, 3];testLoop.test = &#39;hohohooo&#39;;// On loop sur les itérables du tableaufor (const val of testLoop) &#123;    console.log(val); // 1, 2, 3&#125;// On loop sur toutes les propriétés énumérables non Symbolfor (const propt in testLoop) &#123;    console.log(propt); // &quot;0&quot;, &quot;1&quot;, &quot;2&quot;, &quot;test&quot;&#125;</code></pre><p>Autre exemple qui mène souvent à des résultats imprévisibles : lors de l’usage de bibliothèques <em>augmentant</em> certains types natifs et objets.</p><pre><code class="js">// Méthode custom sur le type ArrayArray.prototype.customArrMethod = function() &#123;&#125;;Object.prototype.customObjMethod = function() &#123;&#125;;const testLoop = [1, 2, 3];for (const val of testLoop) &#123;    console.log(val); // 1, 2, 3&#125;for (const propt in testLoop) &#123;    console.log(propt); // &quot;0&quot;, &quot;1&quot;, &quot;2&quot;, &quot;customArrMethod&quot;, &quot;customObjMethod&quot;&#125;</code></pre><p><code>customObjMethod</code> est également listé car un <code>Array</code> est aussi un <code>Object</code>. <code>customObjMethod</code> fait donc dans parti du prototype de <code>testLoop</code>.</p><p>On comprend bien que si l’on utilise <code>for..in</code> pour récupérer les valeurs en faisant <code>testLoop[propt]</code> sans prendre ses précautions, on peut aboutir à quelques bugs inattendus.</p><h2 id="forEach-la-methode-fonctionnelle"><a href="#forEach-la-methode-fonctionnelle" class="headerlink" title=".forEach, la méthode fonctionnelle"></a><code>.forEach</code>, la méthode fonctionnelle</h2><p>Contrairement aux instructions de boucles vues jusqu’ici, <code>.forEach</code> est une méthode définie sur certains types natifs et objets. Cette méthode est disponible par défaut sur nombre d’objets natifs : <code>Array</code>, <code>Set</code>, <code>Map</code>, <code>NodeList</code> et <code>DOMTokenList</code>. Il est tout à fait possible de l’ajouter à vos propres objets, il suffit de manuellement l’implémenter.</p><p>Nous prendrons l’exemple du tableau car il s’agit du type le plus couramment utilisé avec le <code>.forEach</code>. Cette méthode exécute une fonction sur chacun des éléments de son itérable. Trois argument sont passés à la fonction <code>callback</code> :</p><ul><li>la valeur de l’élément courant,</li><li>l’index de l’élement courant,</li><li>l’objet depuis lequel est appelé <code>.forEach</code>.</li></ul><pre><code class="js">const testLoop = [1, 2, 3];testLoop.forEach((el, i, arr) =&gt; &#123;    console.log(el); // 1, 2, 3    console.log(i); // 0, 1, 2    consol.log(arr); // [ 1, 2, 3 ], [ 1, 2, 3 ], [ 1, 2, 3 ]&#125;);</code></pre><p>Optionellement, il est également possible de spécifier le <code>this</code> à utiliser dans le <code>callback</code> :</p><pre><code class="js">array.forEach((el) =&gt; &#123; … &#125;, thisArg);</code></pre><p>Pour un bon nombre de cas d’usage, cette méthode est souvent plus commode que les instructions vues précédemment. En revanche, comme nous le verrons par la suite, certains de ces comportements diffèrent des autres instructions de boucle.</p><h2 id="Instructions-de-controle"><a href="#Instructions-de-controle" class="headerlink" title="Instructions de contrôle"></a>Instructions de contrôle</h2><p>Il existe deux instructions spécifiques permettant de contrôler le comportement des boucles :</p><ul><li><code>break</code> pour l’interrompre,</li><li><code>continue</code> pour passer directement à l’itération suivante.</li></ul><p>Ces deux instructions permettent d’éviter des calculs inutiles lorsqu’il n’est pas nécessaire de continuer l’exécution d’un tour de boucle ou qu’il n’est pas nécessaire de poursuivre les itérations.</p><p>Dans l’exemple suivant, nous cherchons un nombre dans une liste. Une fois que nous avons trouvé ce nombre, il n’est pas utile de poursuivre la boucle, ce sont des cycles de processeur perdus (donc du temps d’exécution).</p><pre><code class="js">let index = 0;const needle = 5;const haystack = [1, 2, 3, 4, 5, 6, 7, 8, 9];for (let i = 0; i &lt; haystack.length; i++) &#123;    if (needle === haystack[i]) &#123;        index = i;        break;    &#125;&#125;</code></pre><p>Dans le cas présent, nous n’avons que peu de valeurs et nous n’effectuons aucun calcul lourd, la différence est donc imperceptible, mais pour un grand set de données ou une exécution complexe dans la boucle, la différence peut être énorme !</p><p>Dans le même ordre d’idée, il arrive parfois qu’il ne soit pas pertinent de poursuivre l’exécution d’un tour de la boucle. Dans ce cas, <code>continue</code> permet de passer directement au tour suivant.</p><pre><code class="js">// méthode naïve de calcul de la suite de fibonaccifunction fibonacci(n) &#123;   if (n &lt; 1) return 0;   if (n &lt;= 2) return 1;   else return fibonacci(n - 1) + fibonacci(n - 2);&#125;// on décide de trouver le nombre de fibonacci pour tous les nombres pairs entre x et y// (x et y non inclus)const x = 10;const y = 30;for (let i = x; i &lt; y; i++) &#123;    // si c&#39;est impair, on passe directement au tour suivant    if (i % 2 !== 0) continue;    console.log(fibonacci(i));&#125;</code></pre><p>Cet exemple est quelque peut tiré par les cheveux, on peut en effet dans ce cas utiliser directement un <code>if</code> pour n’appeler la fonction que si la valeur nous convient. Mais lorsque le corps de la boucle comporte beaucoup de code, cela nous évite des indentations nuisibles à la lisibilité.</p><h3 id="Label-de-bloc"><a href="#Label-de-bloc" class="headerlink" title="Label de bloc"></a>Label de bloc</h3><p>En plus des deux instructions que nous venons de voir, il est possible d’utiliser l’instruction de bloc avec un label. Assez peu connue des développeurs, cette instruction permet d’attribuer un nom à un bloc de code.</p><p><code>break</code> termine par défaut la boucle immédiate, mais si plusieurs boucles sont imbriquées, le <code>break</code> ne met pas fin à la boucle parente. En donnant un label à la boucle parente, c’est possible.</p><p>Dans l’exemple suivant, on cherche à nouveau à identifier une valeur précise dans un tableau, mais ce dernier est cette fois imbriqué. Il nous faut donc trouver la position dans les tableaux parent et enfant.</p><pre><code class="js">let indexParent = 0;let indexChild = 0;const needle = 5;const haystack = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];outer: for (let i = 0; i &lt; haystack.length; i++) &#123;    for (let k = 0; k &lt; haystack[i].length; k++) &#123;        if (needle === haystack[i][k]) &#123;            indexParent = i;            indexChild = k;            break outer;        &#125;    &#125;&#125;</code></pre><p>Sans le label, quand bien même la valeur serait trouvée dans le premier tableau, nous devrions itérer sur toutes les valeurs du parent. Le label nous permet ici d’indiquer à <code>break</code> que nous voulons stopper une boucle de plus haut niveau (en plus de la boucle courante).</p><h3 id="return-the-end"><a href="#return-the-end" class="headerlink" title="return, the end"></a><code>return</code>, the end</h3><p>Le <code>return</code> met fin à la fonction courante (et retourne optionnellement une valeur). À ce titre, il peut être utilisé pour mettre fin à une ou plusieurs boucles.</p><p>Si la ou les boucles sont dans une fonction, la fonction s’arrête alors à ce stade, si nous sommes dans un contexte global ou dans un module, aucun autre code ne sera exécuté.</p><pre><code class="js">const needle = 5;const haystack = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];function findNeedle(needle, haystack) &#123;    for (let i = 0; i &lt; haystack.length; i++) &#123;        for (let k = 0; k &lt; haystack[i].length; k++) &#123;            if (needle === haystack[i][k]) &#123;                return &#123; indexParent: i, indexChild: k &#125;;            &#125;        &#125;    &#125;&#125;findNeedle(needle, haystack);</code></pre><p>Dans le cas du <code>.forEach</code>, étant donné qu’à chaque itération, une nouvelle fonction est lancée, le <code>return</code> ne mettra fin qu’à l’itération courante, à la manière du <code>continue</code> pour les autres structures de boucles.</p><p>Il n’est tout simplement <strong>pas possible d’arrêter</strong> un <code>.forEach</code>.</p><h2 id="Synchrone-et-asynchrone"><a href="#Synchrone-et-asynchrone" class="headerlink" title="Synchrone et asynchrone"></a>Synchrone et asynchrone</h2><p>C’est ici que commencent les choses sérieuses ! Comme vous le savez, le JavaScript est un langage asynchrone. Cela veut dire que le code ne s’exécute par forcément de manière linéaire de haut en bas.</p><p>En l’absence totale de code asynchrone, quelle que soit l’option de boucle choisie, tout se passe sans surprise, le code est exécuté linéairement.</p><p>Prenons pour exemple le calcul d’une suite de Fibonacci des nombres entre 0 et 40 (plus que 40 risque vite de faire planter votre navigateur).</p><pre><code class="js">// on utilise fibonacci pour avoir une fonction qui prend un peu de tempsfunction fibonacci(n) &#123;   if (n &lt; 1) return 0;   if (n &lt;= 2) return 1;   else return fibonacci(n - 1) + fibonacci(n - 2);&#125;// on démarre le chronoconst timeStart = Date.now();for (let i = 0; i &lt; 40; i++) &#123;    // vous pouvez afficher les valeurs returnées si vous le souhaitez    // console.log(fibonacci(i));    fibonacci(i)&#125;// temps total d&#39;exécution,// si le code était asynchrone, on serait proche de zéroconsole.log((Date.now() - timeStart) / 1000);</code></pre><p>Le résultat est le même avec toutes les autres boucles ainsi que le <code>.forEach</code>.</p><pre><code class="js">function fibonacci(n) &#123;   if (n &lt; 1) return 0;   if (n &lt;= 2) return 1;   else return fibonacci(n - 1) + fibonacci(n - 2);&#125;const timeStart = Date.now();Array.from(Array(40).keys()).forEach((i) =&gt; &#123;    fibonacci(i);&#125;);console.log((Date.now() - timeStart) / 1000);</code></pre><p>Il y a plusieurs manières de gérer du code asynchrone, les plus utilisées sont les <code>callback</code>s, les <code>Promise</code>s et les structures <code>async</code>/<code>await</code>. L’exécution du code asynchrone est alors immédiatement lancée et le moteur JavaScript passe à la suite du code, sans attendre le résultat de l’opération.</p><pre><code class="js">function wait() &#123;    return new Promise((resolve) =&gt; &#123;        setTimeout(resolve, 1000);    &#125;);&#125;for (let num of [1, 2, 3]) &#123;    wait().then(() =&gt; console.log(&#39;wait is over&#39;));    console.log(num);&#125;console.log(&#39;loop over&#39;);// 1// 2// 3// &quot;loop over&quot;// &quot;wait is over&quot;// &quot;wait is over&quot;// &quot;wait is over&quot;</code></pre><p>On se rend compte ici que la boucle est intégralement exécutée avant même que la première <code>Promise</code> soit résolue. Ce comportement est tout à fait normal car nous n’avons pas indiqué au moteur JS <strong>d’attendre</strong> le retour du code asynchrone afin de poursuivre son exécution. Le code s’exécute ici exactement de la même manière qu’il le serait en dehors d’une boucle.</p><pre><code class="js">function wait() &#123;    return new Promise((resolve) =&gt; &#123;        setTimeout(resolve, 1000);    &#125;);&#125;async function test() &#123;    for (let num of [1, 2, 3]) &#123;        await wait().then(() =&gt; console.log(&#39;wait is over&#39;));        console.log(num);    &#125;    console.log(&#39;loop over&#39;);&#125;test();// &quot;wait is over&quot;// 1// &quot;wait is over&quot;// 2// &quot;wait is over&quot;// 3// &quot;loop over&quot;</code></pre><p>Cette fois ci, la temporalité du code est bien respectée. L’instruction <code>await</code> fait que la boucle attend la résolution de la promesse lors de chaque itération. Seulement une fois la promesse résolue, la boucle reprend son cours. Ce comportement se vérifie pour l’ensemble des instructions de boucle.</p><p>En revanche, dans le cas de la méthode <code>.forEach</code>, la fonction <code>callback</code> est appelée avec l’ensemble des valeurs sans considération pour la valeur de retour du callback, et donc sans attendre la résolution de la promesse.</p><p>Voici une implémentation simplifiée de la méthode <code>.forEach</code> :</p><pre><code class="js">Array.prototype.forEach = function (callback) &#123;    // ici, this fait référence à l&#39;objet représenté,    // donc au tableau considéré    for (let index = 0; index &lt; this.length; index++) &#123;        // le callback est appelé, ignorant les comportements asynchrones        callback(this[index], index, this);    &#125;&#125;;</code></pre><p>De par sa nature même, <code>.forEach</code> est conçu pour ses effets de bords et non pour sa valeur de retour. Cette dernière ne peut d’ailleurs pas être récupérée (<code>array.forEach()</code> retourne toujours <code>undefined</code>).</p><pre><code class="js">function wait() &#123;    return new Promise((resolve) =&gt; &#123;        setTimeout(resolve, 1000);    &#125;);&#125;function test() &#123;    [1, 2, 3].forEach(async (num) =&gt; &#123;        console.log(&#39;before await&#39;);        await wait().then(() =&gt; console.log(&#39;wait is over&#39;));        console.log(num);    &#125;);    console.log(&#39;loop over&#39;);&#125;test();// &quot;before await&quot;// &quot;before await&quot;// &quot;before await&quot;// &quot;loop over&quot;// &quot;wait is over&quot;// 1// &quot;wait is over&quot;// 2// &quot;wait is over&quot;// 3</code></pre><p>Le <code>await</code> est bien respecté au sein de la fonction <code>callback</code>. Néanmoins, la boucle est intégralement exécutée avant même la résolution de la première promesse.</p><p>Il est toutefois possible de lancer l’ensemble des promesses et d’attendre qu’elles soient ensuite toutes résolues afin d’exécuter la suite d’un programme.</p><pre><code class="js">// tous les traitements sont lancés en parallèle et placés dans un arrayconst promises = [];array.forEach(el =&gt; promises.push(asyncFunction(el)));// lorsque l&#39;ensemble des traitements ont terminé// on peut déclencher la suitePromise.all(promises).then(() =&gt; &#123;    …&#125;);</code></pre><p>Dans ce dernier cas, on préférera parfois le <code>.map</code> au <code>.forEach</code> car il permet de directement récupérer les valeurs.</p><pre><code class="js">const promises = array.map(el =&gt; asyncFunction(el));Promise.all(promises).then(() =&gt; &#123;    …&#125;);</code></pre><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Aucune des manières de faire n’est meilleure qu’une autre. Les quatre instructions de boucle sont fonctionnellement identiques, elles ne diffèrent que par les instructions qu’elles acceptent et le moment auquel elles évaluent la condition.</p><p><code>.forEach</code> diverge de par sa nature et son comportement avec l’asynchrone. Il sera parfois bénéfique de pouvoir lancer l’ensemble des promesses sans en attendre le résultat. On tire ainsi parti de la puissance de la nature asynchrone du JavaScript pour paralléliser les tâches qui requièrent de l’I/O.</p><p>Dans d’autres circonstances, on voudra attendre la résolution d’un traitement précédent afin de déclencher le suivant, cela peut être le cas pour des tâches consommatrices en CPU, RAM ou réseau et afin de ne pas surcharger la machine.</p><p>La richesse et la souplesse du langage nous offrent de nombreuses possibilités pour travailler avec les boucles et les traitements asynchrones. Il ne tient qu’à nous, développeurs, de tirer profit de cette souplesse pour écrire des programmes puissants et performants.</p>]]></content>
    
    <summary type="html">
    
      Il existe une multitude de façon de &quot;looper&quot; sur des valeurs en JavaScript. Les maîtriser et en connaître les différences (synchrone/asynchrone, Promises) vous permettra d&#39;écrire un code plus clair et plus efficace.
    
    </summary>
    
    
    
      <category term="JavaScript" scheme="https://buzut.net/tag/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Bien commenter son code</title>
    <link href="https://buzut.net/bien-commenter-son-code/"/>
    <id>https://buzut.net/bien-commenter-son-code/</id>
    <published>2019-12-02T09:17:24.000Z</published>
    <updated>2024-09-05T06:56:59.836Z</updated>
    
    <content type="html"><![CDATA[<p>Commenter son code, c’est un cadeau que vous envoyez dans le futur. Peut-être pour le prochain développeur qui maintiendra le projet ou vous donnera un coup de main, mais probablement surtout pour votre futur <em>vous</em>.</p><p>En effet, ce qui est limpide dans votre esprit à l’instant où vous écrivez le code, sera clairement plus opaque lorsque quelques semaines ou mois se seront écoulés. La documentation – en plus d’un code bien structuré – participe activement à faire de votre base de code dans son ensemble un “bon code”.</p><span id="more"></span><figure>  <img src="/assets/le-bon-code.jpg" alt="qu'est-ce que le bon code en BD">  <figcaption>Chacun a sa propre définition du bon code, mais un bon code est toujours clair</figcaption></figure><h2 id="Les-commentaires-inline"><a href="#Les-commentaires-inline" class="headerlink" title="Les commentaires inline"></a>Les commentaires inline</h2><p>Il ne s’agit pas d’expliquer ligne par ligne ce que fait le code, loin de là. Si un petit commentaire inline s’avère parfois utile, le code ne doit pas en être truffé.</p><p>Ce type de commentaire permet d’expliquer au plus près du code une particularité de ce dernier, la raison d’être d’un détail ou le pourquoi de l’emploi d’une méthode qui ne semble par forcement évidente.</p><pre><code class="js">function humanDate(date, locales) &#123;    …    const dateYear = dateObj.toLocaleString(locales, &#123; year: &#39;numeric&#39; &#125;);    const dateMonth = dateObj.toLocaleString(locales, &#123; month: &#39;numeric&#39; &#125;);    const dateDay = dateObj.toLocaleString(locales, &#123; day: &#39;numeric&#39; &#125;);    const now = new Date();    const nowYear = now.toLocaleString(locales, &#123; year: &#39;numeric&#39; &#125;);    const nowMonth = now.toLocaleString(locales, &#123; month: &#39;numeric&#39; &#125;);    const nowDay = now.toLocaleString(locales, &#123; day: &#39;numeric&#39; &#125;);    …    // set year only if not the same year as now    if (dateYear !== nowYear) options.year = &#39;numeric&#39;;    // if today, display relative time    if (dateYear === nowYear &amp;&amp; dateMonth === nowMonth &amp;&amp; dateDay === nowDay) &#123;        …    &#125;    …&#125;</code></pre><p>Ici, on comprend tout de suite l’intérêt des commentaires inline, ils précisent de manière univoque le rôle des <code>if</code>, sans qu’il faille méthodiquement se plonger dedans à la lecture pour comprendre ce que font ces conditions.</p><h2 id="Function-level-c’est-la-que-ca-se-passe"><a href="#Function-level-c’est-la-que-ca-se-passe" class="headerlink" title="Function level, c’est là que ça se passe"></a>Function level, c’est là que ça se passe</h2><p>Les commentaires inline permettent d’apporter du contexte, mais c’est bien les commentaires au niveau des fonctions qui représentent la réelle plue-value des commentaires.</p><p>Ils constituent une documentation en eux-même. De nombreux langage ont leur standard, en JavaScript, il s’agit de <a href="https://jsdoc.app/">JSDoc</a>.</p><pre><code class="js">/** * Send custom request using fetch api * @param &#123; String &#125; url * @param &#123; String &#125; method * @param &#123; Object &#125; body * @return &#123; Promise &#125; */function ajax(url, method, body) &#123;    …&#125;</code></pre><p>Grâce à ça, il n’est la plupart du temps même pas utile de se pencher dans la fonction elle-même, à la simple lecture du commentaire, on sait tout de la fonction :</p><ul><li>ce qu’elle fait,</li><li>les paramètres qu’elle accepte,</li><li>la réponse qu’elle retourne.</li></ul><p>Le JSDoc est composé d’un bloc, commençant par <code>/**</code>, ensuite chaque ligne commence par un <code>*</code> et enfin, le bloc est fermé comme n’importe quel commentaire multi-lignes.</p><p>Ce format permettra à votre éditeur d’immédiatement reconnaître le JSDoc et de lui appliquer la coloration syntaxique. Malheureusement, highlight.js, que j’utilise sur ce site, échoue dans cette mission.</p><h3 id="Expliquer-ce-que-fait-la-fonction"><a href="#Expliquer-ce-que-fait-la-fonction" class="headerlink" title="Expliquer ce que fait la fonction"></a>Expliquer ce que fait la fonction</h3><p>La première chose que fait un commentaire JSDoc est d’expliquer en langage humain ce que fait la fonction.</p><p>Bien qu’aucun format ne soit imposé, on tente tout de même de rester concis. Cependant, si cela est nécéssaire, l’explication peut être écrite sur plusieurs lignes. C’est bien plus propre et lisible que d’avoir une seule ligne de 10Km de long !</p><p>Enfin, c’est une question de goût, mais je m’astreins à ce que cette phrase commence toujours par une majuscule, cela indique qu’il s’agit d’une <strong>vraie phrase</strong> construite avec une grammaire valide. Je ne l’utilise pas mais il existe même une <a href="https://eslint.org/docs/rules/capitalized-comments">règle ESLInt</a> pour ça.</p><h3 id="Les-parametres"><a href="#Les-parametres" class="headerlink" title="Les paramètres"></a>Les paramètres</h3><p>On entre ici dans le cœur de la documentation : quels sont les paramètres que la fonction prend en entrée ?</p><p>Ce sujet est plus épineux qu’il ne paraît. En effet, un paramètre possède un type contraint ou peut en accepter plusieurs, il peut être requis ou optionnel, avoir une valeur par défaut, accepter certaines valeurs… Voyons comment exposer ces contraintes de manière claire.</p><p>Pour spécifier qu’on documente un paramètre, on commence toujours la ligne par <code>@param</code>. Ensuite, entre accolades, le type du paramètre, puis son nom et, optionnellement, une description. Notez d’ailleurs que cette phrase peut être séparée du nom du paramètre par un tiret.</p><p>De nouveau, j’ai pour convention de laisser un espace entre le type du paramètre et les accolade (comme dans les objets JavaScript) et de mettre la première lettre du type en majuscule. Rien ne vous y oblige, mais je trouve cela plus clean.</p><p>Par ailleurs, la liste des types n’est pas restreinte aux types natifs JavaScript. Si vous souhaitez dire que le paramètre est un entier, vous pouvez parfaitement mettre <code>Integer</code> car c’est plus précis que le simple type natif <code>Number</code>.</p><p>Cela dit, vous pouvez également le préciser en commentaire.</p><pre><code class="js">* @param &#123; Number &#125; age Age of the user as an integer</code></pre><p>Lorsque je documente des fonctions en front, lesquelles reçoivent des éléments du DOM, je trouve qu’il est plus clair d’indiquer directement de quel type d’élément il s’agit plutôt que de vaguement indiquer qu’il s’agit d’un objet.</p><p>Vous pouvez à ce titre regarder comment je documente les fonctions dans <a href="https://github.com/Buzut/flightdom/blob/master/src/index.js">ƒlightDom</a>, dont l’exemple ci-dessous est tiré.</p><pre><code class="js">/** * Toggle a class on an element * @param &#123; HTMLElement &#125; el * @param &#123; String &#125; className */export function toggleClass(el, className) &#123;    if (hasClass(el, className)) removeClass(el, className);    else addClass(el, className);&#125;</code></pre><h4 id="Multi-types"><a href="#Multi-types" class="headerlink" title="Multi-types"></a>Multi-types</h4><p>C’est un cas qui arrive, un paramètre peut recevoir des valeurs d’un type ou d’un autre, par exemple un nombre ou une chaîne de caractère (laquelle peut représenter un nombre).</p><pre><code class="js">/** * Send an email via SMTP * @param &#123; (String | Array) &#125; to * @param &#123; String &#125; text * @param &#123; String &#125; subject * @return &#123; Promise &#125; */function sendMailWithSMTP(to, subject, text) &#123;    …&#125;</code></pre><p>Il est possible qu’on ne puisse connaître à l’avance le type ou que tous les types soient acceptés, dans ce cas, comme dans les REGEX, on utilise le symbole <code>*</code>.</p><pre><code class="js">* @param &#123; * &#125; response Response of the previous function call</code></pre><p>Dans le cas où le paramètre accepte un type donné ou <code>null</code>, on le signifie à l’aide d’un <code>?</code>.</p><pre><code class="js">* @param &#123; ?Integer &#125; age</code></pre><h4 id="Parametre-optionnel-et-par-defaut"><a href="#Parametre-optionnel-et-par-defaut" class="headerlink" title="Paramètre optionnel et par défaut"></a>Paramètre optionnel et par défaut</h4><p>Si le paramètre est optionnel, on l’entoure de crochets.</p><pre><code class="js">/** * Send client err if it&#39;s validation error or log if it&#39;s a native or low level error * @param &#123; Object &#125; err error object * @param &#123; Object &#125; [res] */function handleError(err, res) &#123;    …&#125;</code></pre><p>Dans cette fonction, il est clair que la paramètre <code>res</code> n’est pas obligatoire. Il existe également des cas où, sans passage du paramètre, on lui assigne une valeur par défaut.</p><pre><code class="js">/** * Signup a new user * @param &#123; String &#125; name * @param &#123; String &#125; password * @param &#123; Integer &#125; age * @param &#123; String &#125; [gender=male] */function createUser(name, password, age, gender = male) &#123;    …&#125;</code></pre><p>Il est à noter qu’il n’est pas obligatoire que le paramètre ayant une valeur par défaut soit optionnel, c’est cependant souvent le cas, question de logique.</p><h4 id="Type-valide-et…"><a href="#Type-valide-et…" class="headerlink" title="Type valide et…"></a>Type valide et…</h4><p>L’autre question à laquelle on doit souvent faire face est la restriction du paramètre, voyons quelques exemples.</p><pre><code class="js">// la valeur doit être une des options de la liste* @param &#123; String=&quot;male, female&quot; &#125; gender// limiter le nombre de caractères d&#39;un string* @param &#123; String&#123;..25&#125; &#125; password// ou l&#39;encadrer entre 10 et 25 par exemple* @param &#123; String&#123;10..25&#125; &#125; password</code></pre><p>La logique sera la même pour un nombre, mais cela précisera sa valeur, pas sa longueur. Un bon exemple concerne les codes postaux français.</p><pre><code class="js">* @param &#123; Integer&#123;10000-99999&#125; &#125; zipcode</code></pre><h4 id="Documenter-les-objets"><a href="#Documenter-les-objets" class="headerlink" title="Documenter les objets"></a>Documenter les objets</h4><p>Lorsque l’on passe un objet en paramètre, il s’avère souvent utile de préciser son contenu. La logique reste la même que ce que nous avons vu jusqu’à maintenant.</p><pre><code class="js">/** * Signup a new user * @param &#123; Object &#125; user * @param &#123; String &#125; user.name * @param &#123; String &#125; user.password * @param &#123; Integer &#125; user.age * @param &#123; String &#125; user.gender */function createUser(user) &#123;    …&#125;</code></pre><p>Il existe une autre forme plus compacte, notamment utile lorsque l’on veut succinctement documenter un <code>return</code>.</p><pre><code class="js">* @param &#123; Object.&lt;name: String, age: Integer&gt; &#125;</code></pre><h4 id="Documenter-les-tableaux"><a href="#Documenter-les-tableaux" class="headerlink" title="Documenter les tableaux"></a>Documenter les tableaux</h4><p>La documentation des tableaux est assez proche de celle des objets. On déclare qu’il s’agit d’un tableau puis les types qu’il contient.</p><pre><code class="js">/** * Compute student&#39;s mean grade * @param &#123; Object[] &#125; students * @param &#123; String &#125; students[].name * @param &#123; Number &#125; students[].mathMean * @param &#123; Number &#125; students[].frenchMean */function computeMean(students) &#123;    …&#125;</code></pre><p>De même que pour les objets, il existe une notation plus compacte,</p><pre><code class="js">// dans le cas où le tableau contient un unique champ* @param &#123; Array.&lt;String&gt; &#125; studentsNames// dans le cas où il est composé d&#39;objets* @param &#123; Array.&lt;Object&gt; &#125; studentsNames// on peut aussi spécifier le contenu de l&#39;objet* @param &#123; Array.&lt;&#123; name: String, age: Integer &#125;&gt; &#125; studentsNames</code></pre><h4 id="Documenter-les-fonctions"><a href="#Documenter-les-fonctions" class="headerlink" title="Documenter les fonctions"></a>Documenter les fonctions</h4><blockquote><p>N’est-ce pas déjà ce dont nous sommes en train de parler</p></blockquote><p>C’est probablement ce que vous vous demandez. Cependant, il s’agit ici des fonctions que nous passons en argument. Comme vous le savez, en JavaScript, les fonctions sont des citoyens de premier rang, les fonctions peuvent donc accepter des fonctions en paramètres et retourner d’autres fonctions en résultats.</p><p>Dans le cas le plus simple, on se contente de déclarer le type comme étant <code>Function</code>. Cependant, un fonction acceptant elle-même des paramètres, il peut être utile de préciser quels sont ces derniers. Notamment lorsqu’on définit un <em>callback</em>.</p><pre><code class="js">/** * Get a user from database * @param &#123; String &#125; userId * @param &#123; Function(err&lt;Error | Null&gt;, user&lt;Object&gt;) &#125; callback */function getUser(userId, (err, user) =&gt; &#123;    …&#125;)</code></pre><p>Alternativement, vous pouvez préciser les paramètres de la fonction dans la description.</p><pre><code class="js">* @param &#123; Function &#125; callback &#123; err, width, ratio, color &#125;</code></pre><h4 id="Nombre-de-parametres-variable"><a href="#Nombre-de-parametres-variable" class="headerlink" title="Nombre de paramètres variable"></a>Nombre de paramètres variable</h4><p>On utilise pour cela la même syntaxe que le spread operator de JavaScript. <code>...parameter</code>. Un exemple concret acceptant un nombre variable de paramètres pouvant être de deux types diférents.</p><pre><code class="js">/** * Check if all DOM elements exist, call ƒn with said els if they exist * @param &#123; Function &#125; fn * @param &#123; ...HTMLElement | ...NodeList &#125; */export function callFnWDomElsIfExist(fn, ...els) &#123;    …&#125;</code></pre><h3 id="Combiner-les-tous"><a href="#Combiner-les-tous" class="headerlink" title="Combiner les tous !"></a>Combiner les tous !</h3><p>Les déclarations sont tout à fait combinables, ainsi vous pouvez déclarer des types composés.</p><pre><code class="js">// date ou array de dates de rendez-vous, également optionnel* @param &#123; Date | Date[] &#125; [appointmentDates]// tableau pouvant contenir des objets ou des strings* @param &#123; Array.&lt;String|Object&gt; &#125; paramsList// paramètre optionnel d&#39;un objet/** * Signup a new user * @param &#123; Object &#125; params * @param &#123; String &#125; params.[gender] */// tableau imbriqué/** * Compute student&#39;s mean grade * @param &#123; Object[] &#125; students * @param &#123; String &#125; students[].name * @param &#123; Number[] &#125; students[].mathGrades * @param &#123; Number[] &#125; students[].frenchGrades */</code></pre><p>La seule limite est votre imagination (et la complexité de votre modèle de données).</p><h3 id="Les-classes"><a href="#Les-classes" class="headerlink" title="Les classes"></a>Les classes</h3><p>Personne n’a oublié que l’ES6 nous a donné les outils pour déclarer des classes. Si vous n’êtes pas au parfum, n’hésitez pas à jeter lire ou relire mon article dédié à la <a href="/programmation-orientee-objet-javascript/">programmation objet en JavaScript</a>.</p><p>Il arrive donc que nous ayons à documenter des classes. Dans une classe, on peut documenter trois niveaux :</p><ul><li>la classe dans son ensemble,</li><li>le constructeur,</li><li>les méthodes.</li></ul><p>Le constructeur et les méthodes se documentent de manière classique, comme n’importe quelle fonction. En revanche, il existe des mots-clefs spécifiques pour la classe.</p><pre><code class="js">/** * @class * @classdesc Create an error for bad HTTP request (contain http status to send err back to the client) */class BadRequestError extends Error &#123;    …&#125;</code></pre><p>D’après la documentation, il est possible de décrire sur la première ligne ce que fait le constructeur, je trouve cependant plus intuitif et direct de le documenter directement à l’intérieur du code de la classe.</p><h3 id="Les-valeurs-de-retour"><a href="#Les-valeurs-de-retour" class="headerlink" title="Les valeurs de retour"></a>Les valeurs de retour</h3><p>Jusque là, nous avons documenté les paramètres des fonctions. Cependant, dans la plupart des cas, une fonction retourne une valeur. Si ce n’est pas le cas, c’est que votre fonction est impure, qu’elle a donc des effets de bord. C’est parfois inévitable, mais c’est à éviter au maximum dans un contexte de <a href="/programmation-fonctionnelle-en-javascript/">programmation fonctionnelle</a>.</p><p>Dans le cas où la valeur de retour est simple, il suffit de préciser le type en utilisant <code>@return</code>.</p><pre><code class="js">/** * Get the first matching element * @param &#123; String &#125; selector CSS selector * @return &#123; HTMLElement &#125; */export function find(selector) &#123;    return document.querySelector(selector);&#125;</code></pre><p>En outre, une fonction peut retourner différentes valeurs selon le contexte, par exemple un nombre en cas de succès ou une erreur en cas de problème. Là encore, la logique vu jusqu’alors s’applique.</p><pre><code class="js">* @return &#123; (Number | Error) &#125;</code></pre><p>Par ailleurs, on a de plus en plus recours aux promesses, la fonction retourne donc une promesse, laquelle peut être dans plusieurs états :</p><ul><li>en attente,</li><li>résolue,</li><li>rejetée.</li></ul><p>Dans le cas le plus simple, on spécifie simplement que la fonction retourne une promesse. On sait dès lors, car c’est la nature même d’une promesse qu’elle peut réussir ou échouer.</p><pre><code class="js">/** * Get comments for a given article id (w/ authors names) * @param &#123; Number &#125; articleId * @return &#123; Promise &#125; */function getForId(id) &#123;    …&#125;</code></pre><p>Cependant, dans de nombreux cas, il est souhaitable de documenter les différents états de manière plus précise. Dès lors, on adopte la même syntaxe que pour documenter les objets.</p><pre><code class="js">/** * Approve comment * @param &#123; Number &#125; commentId * @param &#123; String &#125; userSecret * @return &#123; Promise &#125; * @return &#123; Promise.resolve&lt;String&gt; &#125; articleId * @return &#123; Promise.reject&lt;Error&gt; &#125; knex Err or BadRequestError */function approve(commentId, userSecret) &#123;    …&#125;</code></pre><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Nous avons vu comment commenter la plupart des cas que vous rencontrerez. Les commentaires vous permettent non seulement d’améliorer votre compréhension du code et de relire et maintenir plus vite et efficacement une base de code, mais aussi de générer une vraie documentation.</p><p>Par exemple, dans le cas d’une bibliothèque à destination d’autres développeurs, vous pouvez générer une documentation grâce à <a href="https://jsdoc.app/">JSdoc</a>. Une application en CLI vous permet de dynamiquement générer la doc liée à votre projet. C’est ce que j’utilise pour la <a href="https://buzut.github.io/flightdom/">documentation de ƒlightDom</a>.</p><p>Dans la même veine, vous pouvez documenter vos API avec <a href="https://apidocjs.com/">APIdoc</a>. C’est la solution que j’utilise pour documenter les endoints de <a href="https://buzut.github.io/jamments/api/">Jamments</a>.</p>]]></content>
    
    <summary type="html">
    
      Commenter le code est une bonne pratique, mais c&#39;est surtout un indispensable qui vous fera gagner énormément de temps. Le premier bénéficiaire, c&#39;est vous ! Commenter c&#39;est documenter, voyons comment.
    
    </summary>
    
    
    
      <category term="JavaScript" scheme="https://buzut.net/tag/JavaScript/"/>
    
      <category term="Réflexions" scheme="https://buzut.net/tag/Reflexions/"/>
    
      <category term="Outils" scheme="https://buzut.net/tag/Outils/"/>
    
  </entry>
  
  <entry>
    <title>Workflow multi-machines : pourquoi et comment ?</title>
    <link href="https://buzut.net/travailler-sur-plusieurs-machines-pourquoi-comment/"/>
    <id>https://buzut.net/travailler-sur-plusieurs-machines-pourquoi-comment/</id>
    <published>2019-10-21T23:31:50.000Z</published>
    <updated>2024-09-05T06:56:59.840Z</updated>
    
    <content type="html"><![CDATA[<p>Si l’on veut être productif, il faut que nos outils le soient aussi. Pour ma part, en plus de l’environnement logiciel et matériel de la machine principale déjà expliqué dans un précédent article, j’estime que plusieurs machines sont nécessaires à ma productivité.</p><p>En tant que développeurs, nos outils sont de deux catégories :</p><ul><li>les outils logiciel,</li><li>les outils matériel.</li></ul><span id="more"></span><blockquote><p>Que l’on me donne six heures pour couper un arbre, j’en passerai quatre à préparer ma hache.</p></blockquote><p><cite>Lincoln</cite></p><p>Il ne s’agit pas là de consumérisme débridé, j’essaye toujours au mieux de <a href="/linux-contre-lobsolescence-programmee/">faire durer mon matériel</a>.</p><h2 id="La-tablette"><a href="#La-tablette" class="headerlink" title="La tablette"></a>La tablette</h2><p>De nombreuses personnes ont aujourd’hui délaissé l’ordinateur pour ne plus utiliser que tablette ou téléphone. Pour nous, développeurs, ce n’est tout simplement pas envisageable.</p><p>Il est possible de <a href="/travailler-sur-tablette-cest-possible/">travailler sur tablette</a> dans certaines conditions, mais il est tout bonnement impensable d’en faire sa machine de travail principale.</p><p>Néanmoins, une bonne partie de notre travail consiste à faire de la veille et à se former. Cette partie est donc principalement constituée de <em>lecture</em>. Et pour la lecture une tablette est idéale.</p><p>J’ai donc un iPad qui me sert tous les jours pour faire ma veille, j’y lis Haker News, le <a href="https://www.journalduhacker.net/">Journal du Hacker</a>, Smashing Magazine, CSS Tricks etc. Ces moments de lecture - qui font partie intégrante d’une journée de travail - sont tout de même plus confortables dans un canapé, ou même dehors quand le temps le permet.</p><p>On passe en effet déjà énormément d’heures assis à notre bureau, changer un peu d’endroit et de position ne peut que nous faire le plus grand bien.</p><h2 id="La-seconde-machine"><a href="#La-seconde-machine" class="headerlink" title="La seconde machine"></a>La seconde machine</h2><p>Certains ont un ordinateur fixe en machine principale, ce qui n’est pas mon cas, car je bouge beaucoup.</p><p>Ma machine de développement principal est un MacBook Pro 15”. J’en suis très content et, la plupart du temps, il fait office d’unité centrale, posé sur un bureau, branché sur deux écrans externes et relié à un clavier ainsi qu’à une souris.</p><p><img src="/assets/bureau.jpg" alt="Ma machine principale"></p><p>Cette machine est confortable et puissante. Son seul problème est qu’elle est assez volumineuse et comme elle est toujours branchée, avec des fenêtres réparties sur trois écrans, je suis toujours réticent à la bouger si ce n’est pas pour partir (déplacement, voyage…).</p><p>J’apprécie donc d’avoir une deuxième machine, un ordinateur plus compact et léger. Je m’en sers lorsque je dois brièvement partir pour un rendez-vous ou pour travailler hors du bureau.</p><p>S’il fait beau et que je veux m’installer dehors, ou le soir pour écrire un article depuis le canapé ou mon lit. Cette machine n’est pas nécessairement puissante. J’utilise un Dell XPS qui a presque dix ans. Il tourne sous <a href="/linux-contre-lobsolescence-programmee/">Solus OS et fonctionne à merveille</a>.</p><h2 id="Gerer-la-diversite"><a href="#Gerer-la-diversite" class="headerlink" title="Gérer la diversité"></a>Gérer la diversité</h2><p>L’inconvénient de jongler entre plusieurs machines, c’est qu’on ne retrouve pas toujours tout si l’on n’est pas bien organisé.</p><p>Fort heureusement, l’époque où l’on tentait de rester synchronisé à coup de clef USB et de pièce jointe d’email est révolue.</p><p>Il y a 3 composantes que j’aime avoir à portée de main et synchronisées sur tous mes appareils :</p><ul><li>mes favoris,</li><li>mes emails, calendrier et contacts,</li><li>mes documents (principalement sur les ordinateurs).</li></ul><h3 id="Emails-contacts-calendriers-et-favoris"><a href="#Emails-contacts-calendriers-et-favoris" class="headerlink" title="Emails, contacts, calendriers et favoris"></a>Emails, contacts, calendriers et favoris</h3><p>Je n’ai pas mentionné le smartphone jusque là, mais c’est une évidence, tout le monde en a un, et en plus de me servir pour les emails, je l’utilise pour lire lorsque j’ai des temps morts.</p><p>J’utilise principalement Firefox en navigateur et Safari sur mes appareils iOS. Je ne suis pas fan des favoris des navigateurs, ça manque un peu d’organisation pour moi.</p><p>Pour cela, j’utilise <a href="https://github.com/shaarli/Shaarli">Shaarli</a> avec un thème <a href="https://github.com/kalvn/Shaarli-Material">material design</a>, car le thème d’origine n’est pas à mon goût. Grâce à cela, j’ai mes favoris sur absolument tous mes appareils, et même ceux qui ne sont pas à moi, car il s’agit d’un site internet.</p><p>Les contacts et le calendrier sont synchronisés par <a href="https://nextcloud.com/">NextCloud</a>. Les protocoles sont des standards, donc ça peut se synchroniser avec à peu près n’importe quoi, c’est la beauté des standards. Idem pour l’email, imap/smtp, ça se synchronise sur tous les clients.</p><h3 id="Synchro-des-fichiers"><a href="#Synchro-des-fichiers" class="headerlink" title="Synchro des fichiers"></a>Synchro des fichiers</h3><p>Là où ça se complique, c’est pour les documents. J’ai mentionné Nextcloud, je l’utilise pour les documents également. Il est dispo sur Linux, Mac et PC, donc pas de problème à ce niveau là.</p><p>Il y a aussi la possibilité de monter le disque en disque distant, à nouveau, c’est du standard : WebDAV. Vous n’aurez alors rien à installer si votre système le supporte (c’est le cas de tous les OS principaux). En revanche, notez que les performances ne sont pas aussi bonne et vous ne pouvez pas écrire sur le disque en étant hors connexion.</p><p>NextCloud (ou les drives de manière générale) sont vraiment très pratiques car leur usage est transparent. En revanche, dès lors que vous avez de très nombreux petits fichiers, ça devient un peu moins efficace.</p><p>Je désactive donc la synchro de tous les dossiers qui s’appellent “Code” - j’en ai en général un par projet ainsi que des “node_modules”. Ils sont de toute façons toujours dans “Code”, mais sait-on jamais.</p><p>Dès qu’il s’agit de code, <a href="/cours/versioning-avec-git/">Git</a> prend la relève avec <a href="https://gogs.io/">Gogs</a> comme serveur et interface web. Mes deux ordinateurs partagent la même clef ssh, donc je suis authentifié de la même façon qu’il s’agisse du portable Mac ou Linux.</p><p>Il faut faire bien attention lorsque vous transférez cette clef d’une machine à l’autre. Soit on utilise un support chiffré, soit un efface <strong>bien</strong> la clef usb après usage. Et en aucun cas on ne s’envoie ça par email ou un cloud !</p><p>Une fois que l’on a tout cela de bien synchronisé, la dernière petite étape consiste à mapper les quelques fichiers de config que l’on veut garder synchro et qui vivent en dehors du cloud.</p><p>En ce qui me concerne, quelque coups de <code>ln</code> suffisent à garder synchronisé mon <em>ssh config</em> ainsi que mes <em>hosts</em> Ansible. De cette manière, d’une machine ou d’une autre, je suis en mesure de me connecter à tous mes serveurs !</p><h4 id="Les-mots-de-passe"><a href="#Les-mots-de-passe" class="headerlink" title="Les mots de passe"></a>Les mots de passe</h4><p>Les mots de passes sont des fichiers particuliers dans la mesure où ils nécessitent une attention accrue et une gestion très fine des accès.</p><p>Il n’est ni question d’avoir le même mot de passe partout, ni question de consigner sa liste de mot de passe dans un fichier sauvegardé sans plus de précautions.</p><p>J’utilise à cet effet un <a href="https://fr.wikipedia.org/wiki/Gestionnaire_de_mots_de_passe">gestionnaire de mot de passe</a>. De nombreux gestionnaires existent. J’ai pour ma part choisi les standards avec le chiffrement via <a href="https://en.wikipedia.org/wiki/GNU_Privacy_Guard">GPG</a> et je les fonctionalités propres à la gestion de mot de passe via le logiciel cross-plateforme open source <a href="https://qtpass.org/">QtPass</a>.</p><p>Avec QtPass, un fichier est créé pour chaque site ou logiciel, avec toutes les informations afférentes. Il est possible de préciser dans quel répertoire stocker les mots de passe et ainsi les synchroniser sur le cloud de manière totalement sécurisée : sans le mot de passe “maître”, rien n’est lisible.</p><p>Par ailleurs, si j’ai besoin de déchiffrer l’un de ces fichier, je peux le faire depuis n’importe quelle machine ayant GPG installé. Je ne suis pas bloqué avec un gestionnaire en particulier. QtPass m’offre une interface efficace, spécifiquement pensée pour la gestion des mots de passe et m’évite d’utiliser la ligne de commande dans cette tâche quotidienne.</p><p>En revanche, je ne peux pas accéder à mes mots de passe depuis ma tablette ou mon mobile. Il est peut être possible de mettre cela en place, mais n’en n’ayant pas le besoin, je n’ai pas effectué de recherches.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Si les appareils sont différents, il n’est pas forcement possible de répliquer l’environnement de partout. Cependant, de nombreux logiciels sont multi-plateformes (Windows, macOS, Linux).</p><p>Par exemple, pour ce qui concerne la programmation, mon shell bash et mon éditeur de code, Atom, ont exactement les mêmes configurations sur toutes mes machines.</p><p>Le reste est bien synchronisé et accessible depuis tous mes devices, sans aucun accroc depuis de nombreuses années. Testé et approuvé !</p><p><em>N’hésitez pas à partager vos outils et votre organisation ou à exprimer des besoins particuliers dans les commentaires.</em></p>]]></content>
    
    <summary type="html">
    
      Si vous jonglez entre plusieures ordinateurs, tablettes et smartphones, vous savez qu&#39;avoir ses données toujours à disposition représente un énorme gain de productivité. Voyons comment maintenir tous vos appareils synchronisés.
    
    </summary>
    
    
    
      <category term="Outils" scheme="https://buzut.net/tag/Outils/"/>
    
  </entry>
  
  <entry>
    <title>Les modules JavaScript indispensables</title>
    <link href="https://buzut.net/modules-javascript-indispensables/"/>
    <id>https://buzut.net/modules-javascript-indispensables/</id>
    <published>2019-09-01T11:17:13.000Z</published>
    <updated>2024-09-05T06:56:59.840Z</updated>
    
    <content type="html"><![CDATA[<p>L’un des motos de tout bon développeur est DRY, <em>don’t repeat yourself</em>. Pourtant, une très grosses partie de nos projets ont des besoins similaires : valider un email, uploader des fichiers…</p><p>À ce titre, conformément à un autre moto de tout bon développeur – <em>ne pas réinventer la roue</em> – on utilise des outils et modules déjà existants et éprouvés. Voici donc un petit listing des modules que j’utilise régulièrement.</p><span id="more"></span><p>Nous parlons ici de JavaScript, il va donc sans dire que tous ces outils se trouvent sur <a href="/npm-for-everything/">npm</a>, le package manager du monde JS.</p><p>Par ailleurs, vous avez aussi très certainement vécu la situation où vous vous retrouvez à copier d’un projet à un autre des petits helpers ou lib maison. C’est donc peut-être le moment d’en faire un <a href="https://github.com/Buzut/npm-module-boilerplate">module npm</a> et de le publier afin de pouvoir l’utiliser plus facilement.</p><p>À ce titre, certains modules de ce listing sont des modules que j’ai moi-même développé et mis à disposition de tous sur npm.</p><h2 id="Modules-front-end"><a href="#Modules-front-end" class="headerlink" title="Modules front-end"></a>Modules front-end</h2><p>Je parle ici des modules qui sont utilisés pour le front et se retrouvent dans le build final envoyé au navigateur du client.</p><ul><li><a href="https://github.com/Buzut/humantime">humanTime</a> est un module qui permet d’afficher des dates dans un format human readable. Le format s’adapte en fonction de la date – par exemple “il y a 37 mn” ou “le 24 décembre 2017”, selon la récence de la date – et des préférences de langue navigateur de l’internaute (ça repose sur  l’API d’Internationalisation des navigateurs).</li><li><a href="https://github.com/Buzut/autotimer">autoTimer</a> est un petit module fournissant un timer automatiquement unbounced. C’est à dire que s’il est appelé plusieurs fois avant le timeout défini, seul le dernier appel sera exécuté. C’est très utile pour tout ce qui s’exécute suite aux fonctions d’évènements (frappe clavier, resize du navigateur…).</li><li><a href="https://github.com/Buzut/resizeimg">resize-image</a> permet de compresser (et automatiquement cropper) des images dans le navigateur afin de pouvoir les uploader plus rapidement.</li><li><a href="https://github.com/henrygd/bigpicture">BigPicture</a> est un module lightbox permetant de visualiser photos et vidéos. Il est léger et offre des transitions morphing de type <em>scale to fit</em> du plus bel effet.</li><li>similar to BigPicture, <a href="https://photoswipe.com">PhotoSwipe</a> supports fullscreen and offers more controls over the gallery.</li><li><a href="https://github.com/Buzut/huge-uploader">huge-uploader</a> gère les uploads de très gros fichiers de manière intelligente en “coupant” le fichier et en l’envoyant par petits bouts. De cette manière, il est possible de mettre en pause l’upload et les erreurs du réseau ne mettent pas l’upload en échec.</li><li><a href="https://github.com/Buzut/flightdom">ƒlightDom ou functional light DOM</a> est une bibliothèque destinée à faciliter l’utilisation du DOM de manière fonctionnelle. Depuis que les navigateurs modernes nous permettent de <a href="https://buzut.net/apres-jquery-dom-natif-et-alternatives/">nous passer de jQuery</a>, on travaille souvent directement avec les API natives du DOM. Leur syntaxe est souvent lourde et non adaptée à la programmation fonctionnelle. Ce module permet donc d’adresser cette situation avec une API fonctionnelle et légère.</li><li><a href="https://fusejs.io/">Fuse.js</a> est un module extrêmement léger pour faire des recherches dans des tableaux ou objets de manière très performante. La recherche reste fonctionnelle malgré quelques fautes de frappe. À titre indicative, c’est ce qui est utilisé pour la recherche de ce blog.</li><li><a href="https://roughjs.com">Rough.js</a> permet de dessiner (Canvas et SVG) dans un style “crayon”. Il possède de nombreuses formes prédéfinies mais ses possibilités sont illimités.</li><li>Base sur le précédent, <a href="https://roughnotation.com">RoughNotation</a> permet de de créer des annotations de texte (entourer, barrer, sur-ligner, sous-ligner…) dans un style “main levée”.</li></ul><p>Comme je travaille beaucoup avec Vue.js, il y a certains modules front forts utiles qui sont spécifiques à Vue. Les voici.</p><ul><li><a href="https://github.com/Dafrok/v-hotkey">V-Hotkey</a> permet de facilement binder des touches ou combinaisons de touches à un comportement. Pratique pour mettre en place des raccourcis clavier.</li><li><a href="https://github.com/ndelvalle/v-click-outside">V-click-outside</a> permet à un composant d’être averti lorsqu’un click est effectué à l’extérieur de celui-ci. On l’utilisera pour fermer un champ de recherche autocomplete, une lightbox ou mettre en pause un player vidéo.</li><li><a href="https://github.com/euvl/v-clipboard">V-Clipboard</a> permet de copier un texte dans le presse-papier de l’utilisateur.</li></ul><h2 id="Modules-back-end"><a href="#Modules-back-end" class="headerlink" title="Modules back-end"></a>Modules back-end</h2><p>Le but n’est pas ici de lister <strong>tous</strong> les modules que l’on utilise régulièrement en back, mais plutôt de mettre en évidence ceux qui sont pratiques et que l’on a pas tout de suite en tête.</p><ul><li>Valider une adresse email est une opération que nous avons constamment à faire. Aussi simple que cela puisse paraître, il est possible d’appliquer plusieurs niveaux de vérification : validation syntaxique, vérification des enregistrements DNS et validation SMTP. Si la première étape se fait avec une simple REGEX, les autres permettent d’aller plus loin. <a href="https://www.npmjs.com/package/isemail">isemail</a> permet d’appliquer une simple vérification syntaxique, avec un algorithme plutôt avancé. <a href="https://gitlab.com/autokent/email-domain-check">email-domain-check</a> vérifie si le domaine possède des enregistrements MX. Enfin, <a href="https://github.com/getconversio/email-deep-validator">email-deep-validator</a> vous fait la totale (REGEX, MX et SMTP) !</li><li><a href="https://expressjs.com/">Express.js</a> est <strong>le</strong> framework web de Node.js. Son usage n’est pas impératif mais il apporte son lot d’outils très utiles et aide à structurer le code dès lors que le projet n’est pas trivial.</li><li><a href="https://knexjs.org/">knex.js</a> est un SQL query builder. À ce titre, il permet simplement de gérer la plupart des bases de données SQL (MSSQL, MySQL, Postgres, SQLite). Ainsi, si vous changez de base de données en cours de route, votre code reste le même. C’est aussi également très pratique pour un projet qui veut offrir une compatibilité maximale à ses utilisateurs. C’est ce que j’utilise pour <a href="https://github.com/Buzut/jamments">Jamments</a>. On profite également d’une syntaxe familière pour ceux qui sont un peu allergiques au SQL.</li><li><a href="https://github.com/mysqljs/mysql">mysqljs</a> permet à l’inverse, d’écrire du SQL directement pour MySQL ou MariaDB. Il y a plusieurs clients possibles pour MySQL, mais celui-ci est mon préféré. Très performant, son API est simple et logique. On peut faire du bulk insert, streamer les résultats, profiter du pseudo <code>UPSERT</code> avec <code>ON DUPLICATE KEY UPDATE</code>… Il ne lui manque pour l’instant que les promises, mais un petit wrapper fait l’affaire dans 99% des cas.</li><li><a href="https://github.com/Buzut/bad-request-error">bad-request-error</a> est un constructeur d’erreur dédié aux erreurs dans les paramètres de requête HTTP. On lui passe un message d’erreur ainsi que le code d’erreur HTTP. Il vise à permettre l’automatisation des réponses HTTP au client.</li><li><a href="https://github.com/Buzut/express-body-validator">express-body-validator</a> est un module de validation des paramètres des requêtes HTTP : <strong>never trust user input</strong> ! Orienté promise et basé sur v8n, ce module rend la validation des paramètres aussi simple qu’efficace. Les données sont passées directement à <code>then</code> en cas de succès, et si un paramètre n’est pas validé, on <code>catch</code> une erreur BadRequestError. Fini les conditions et REGEX qui polluent vos controlleurs, le flux des données est maintenant préservé. Le module existe aussi pour Node.js sans framework : <a href="https://github.com/Buzut/node-body-validator">node-body-validator</a>.</li><li><a href="https://github.com/eleith/emailjs">emailjs</a> permet d’envoyer des emails directement en SMTP, une tâche elle aussi assez récurrente en back.</li><li><a href="https://github.com/Buzut/huge-uploader-nodejs">huge-uploader-nodejs</a> est la partie serveur du module huge-uploader listé dans la partie frontend. Il réceptionne les “morceaux” envoyés par le navigateur et les assemble à la fin de l’upload afin de récupérer le fichier complet.</li><li>Dans le domaine des modules d’upload de fichiers, plusieurs options existent côté Node.js. Les quatre principales solutions sont <a href="https://github.com/expressjs/multer">Multer</a>, <a href="https://github.com/mscdex/busboy">Busboy</a>, <a href="https://github.com/node-formidable/formidable">Formidable</a> et <a href="https://github.com/pillarjs/multiparty">Multiparty</a>. Chaque module présente des avantages et inconvénients. Vous pouvez consulter <a href="https://bytearcher.com/articles/formidable-vs-busboy-vs-multer-vs-multiparty/">cet article [en]</a> pour faire un choix avisé.</li><li><a href="https://github.com/winstonjs/winston">winston</a> se passe presque de présentation. C’est le logger roi de Node.js, on ajoute les transports (plugins) qui vont bien et les logs partent vraiment où vous voulez (fichier, ELK, Graylog…).</li><li><a href="https://github.com/Buzut/winston-log2gelf">winston-log2gelf</a> est un transport Winston pour envoyer vos logs vers un serveur Graylog ou GELF.</li></ul><h2 id="Modules-isomorphiques"><a href="#Modules-isomorphiques" class="headerlink" title="Modules isomorphiques"></a>Modules isomorphiques</h2><p>Nous parlons ici des modules que l’on utilise aussi bien en front qu’en back. Je considère plus le cas d’usage que le potentiel technique à tourner en front ou back.</p><ul><li><a href="https://www.npmjs.com/package/uniqid">uniqid</a> permet, comme son nom l’indique, de générer des id uniques, de manière similaire à la fonction éponyme de PHP. Pour des besoins plus complexes avec des ids de longueur personnalisable (plus longs, ou plus courts), on peut se diriger vers <a href="https://github.com/ai/nanoid">Nono ID</a>.</li><li><a href="https://imbrn.github.io/v8n/">v8n</a> pour <em>validation</em>, est un module permettant de valider les données (type, valeur etc). Il remplace très avantageusement des conditions et des regex. De plus, son api est chainable, ce qui la rend vraiement conviviale à l’usage pour crééer des règles de validation.</li><li><a href="https://github.com/chriso/validator.js">validator</a> est un autre module de validation. Contrairement au précédent, il ne permet pas de créer des règles de validation mais propose un set de règles prédéfinis pour les cas souvent rencontrés : numéro de téléphone (nombreux pays disponibles), numéro de carte bancaire, email, url…</li><li><a href="https://socket.io/">socket.io</a> n’est pas à proprement parler isomorphique, mais une partie s’exécute dans le navigateur tandis qu’une autre tourne sur le serveur. Cette librairie – qu’on ne présente plus – est une surcouche à l’api websockets, qui permet facilement de tirer profit de cette technologie full duplex. La bibliothèque fallback automatiquement sur du polling si la configuration (réseau ou navigateur) ne permet pas l’usage des websockets.</li><li><a href="https://github.com/colorjs">color.js</a> Il s’agit ici d’une collection de modules pour travailler avec les couleurs. La majorité – je ne sais pas si c’est le cas de tous – tournent aussi bien en front qu’en back.</li></ul><h2 id="Modules-de-developpement"><a href="#Modules-de-developpement" class="headerlink" title="Modules de développement"></a>Modules de développement</h2><p>Ce sont ici les modules qui restent sur nos machines de dev, mais dont on pourrait difficilement se passer… Certains de ces modules s’utilisent de manière différentes selon que l’on travaille sur un modèle de pages “classiques” ou avec un bundler tel que Webpack.</p><p>Je vais donc commencer par l’usage classique – lequel est détaillé dans mon <a href="/configurer-rollup-bundles-esm-cjs/">article sur Rollup</a> – puis on verra comment gérer les choses avec Webpack (dans un cadre type Vue.js ou React).</p><ul><li><a href="https://eslint.org/">eslint</a> le linter JavaScript, celui-ci est dans absolument tous mes projets. Il se marie idéalement avec <a href="https://www.npmjs.com/package/eslint-config-airbnb-base">eslint-config-airbnb-base</a>, on lui adjoint aussi <a href="https://www.npmjs.com/package/eslint-plugin-import">eslint-plugin-import</a> afin de bien valider la syntaxe ESModules. De plus, pour les projets Node.js, on peut également avantageusement utiliser <a href="https://github.com/nodesecurity/eslint-plugin-security">eslint-plugin-security</a>, lequel permet de détecter certaines erreurs menant à des failles de sécurités.</li><li><a href="https://www.npmjs.com/package/stylelint">stylelint</a> même chose mais côté styles. Je l’utilise conjointement à <a href="https://www.npmjs.com/package/stylelint-config-standard">stylelint-config-standard</a> afin d’avoir une config de base qui fait sens et <a href="https://www.npmjs.com/package/stylelint-order">stylelint-order</a> afin de forcer un ordre dans les styles. C’est meilleur pour la compression et pour s’y retrouver. Là il y a deux écoles : ceux qui veulent de l’alphabétique et ceux qui veulent grouper les choses sémantiquement proche (<code>width</code> et <code>height</code> devraient être à côté…).</li><li><a href="https://github.com/postcss/postcss-cli">postcss-cli</a> permet, toujours au rang des styles, de faire énormément de choses selon les plugins qu’on luit adjoint. Je considère comme indispensable de l’utiliser avec <a href="https://www.npmjs.com/package/autoprefixer">autoprefixer</a>.</li><li><a href="https://www.npmjs.com/package/less">less</a> permet de compiler le LESS en CSS. Bien que les récentes version de CSS permettent beaucoup, je reste un utilisateur habitué au confort de LESS. On verra dans quelques années si la situation évolue.</li><li><a href="https://www.npmjs.com/package/cssmin">cssmin</a> est indispensable afin de compresser les styles qui seront envoyés à nos visiteurs.</li><li><a href="https://www.purgecss.com/">purgecss</a> parse vos fichiers CSS et supprime tout ce qui n’est pas utilisé. Un outil qui fait fondre vos feuilles de style comme neige au soleil.</li></ul><!-- Je dédis un [article à son usage](/optimiser-css-avec-purgecss/). --><h3 id="Rollup"><a href="#Rollup" class="headerlink" title="Rollup"></a>Rollup</h3><p>Là où on utilisait avant <a href="http://browserify.org/">Browserify</a> avec le CommonJS, on aura maintenant plutôt tendance à utiliser un module bundler capable de gérer les module ECMAScript.</p><p>J’utilise pour ma part <a href="https://rollupjs.org/guide/en/">Rollup</a>. On l’installe avec ses indispensables plugins que sont :</p><ul><li><a href="https://github.com/rollup/rollup-plugin-node-resolve">node-resolve</a> afin d’importer des modules installés via npm,</li><li><a href="https://github.com/rollup/rollup-plugin-commonjs">commonjs</a> afin de prendre en charge les modules qui ne sont qu’au format commonjs (<code>require</code>),</li><li><a href="https://github.com/rollup/rollup-plugin-babel">babel</a> et <a href="https://babeljs.io/docs/en/babel-preset-env">@babel/preset-env</a> pour compiler du code moderne vers une cible plus large,</li><li>et <a href="https://github.com/TrySound/rollup-plugin-terser">terser</a> afin de minifer le code pour le rendre plus léger à télécharger.</li></ul><h3 id="Webpack-amp-Vue-js"><a href="#Webpack-amp-Vue-js" class="headerlink" title="Webpack &amp; Vue.js"></a>Webpack &amp; Vue.js</h3><p>Dès lors que le projet est un peu plus complexe (ou selon les préférences), on a tendance à utiliser Webpack. C’est par exemple le bundler par défaut avec Vue CLI et il accompagne quasi automatiquement les projets <a href="https://webpack.js.org/">React</a>.</p><p>Bon nombre des dépendances sont directement installées et configurées par <a href="https://cli.vuejs.org/">Vue CLI</a> lorsqu’on l’utilise pour initialiser un projet. Il n’est donc pas forcement utile de les détailler ici car elles ne sont pas manuellement installées.</p><ul><li><a href="https://github.com/webpack-contrib/less-loader">less-loader</a> permet de charger et convertir les fichiers LESS.</li><li><a href="https://github.com/webpack-contrib/worker-loader">worker-loader</a> permet de charger comme il se doit les web workers.</li></ul><p>Comme je l’ai mentionné en introduction, il ne s’agit absolument pas d’une liste exhaustive de tous les modules utiles ou que j’utilise, mais plutôt de ceux que l’on retrouve presque systématiquement dans la plupart des typologies de projets. N’hésitez pas à me faire part de vos propres préférences et conseils en commentaires.</p>]]></content>
    
    <summary type="html">
    
      Le JavaScript possède un large écosystème de modules permettant d&#39;accélérer le développement. Le plus difficile est de trouver les bons. Découvrez les modules indispensables dont vous ne pourrez plus vous passer !
    
    </summary>
    
    
    
      <category term="JavaScript" scheme="https://buzut.net/tag/JavaScript/"/>
    
      <category term="Outils" scheme="https://buzut.net/tag/Outils/"/>
    
  </entry>
  
  <entry>
    <title>PSTconverter: convertir les archives Exchange/Outlook</title>
    <link href="https://buzut.net/pstconverter-convertir-les-archives-exchange-outlook/"/>
    <id>https://buzut.net/pstconverter-convertir-les-archives-exchange-outlook/</id>
    <published>2019-08-22T07:00:00.000Z</published>
    <updated>2024-09-05T06:56:59.840Z</updated>
    
    <content type="html"><![CDATA[<p>Au pringtemps 2018, ma mère avait besoin de récupérer le contenu d’une archive PST Exchange de plus de 50GB. Impossible d’importer ça sur Linux, pas possible non plus sur les autres systèmes sans s’abonner à Office 365. Je fais quelques recherches et entre 400 logiciels Windows payants, je finis par trouver un outil libre en ligne de commande.</p><p>Ce logiciel s’appelle readpst et fait partie de la collection d’outils <a href="http://www.five-ten-sg.com/libpst/">libpst</a>. Je fais la conversion et tout fonctionne à merveille. Le seul bémol : j’imagine mal ma chère maman installer et utiliser un logiciel en CLI. De là me vient l’idée d’en faire une solution web : <a href="https://pstconverter.net/">PSTconverter</a>.</p><span id="more"></span><p>Une seule question : pourquoi une solution web ? Tout simplement parce que j’installe le minimum de choses sur ma machine. De plus, l’idée d’installer un logiciel que je n’utiliserai qu’une seule fois me donne des boutons. D’autant plus qu’il s’agit pour la plupart de logiciels qui donnent moyennement confiance.</p><p>L’objectif est donc de fournir un outil en ligne permettant très simplement la conversion d’archives PST vers MBOX.</p><h2 id="La-technique"><a href="#La-technique" class="headerlink" title="La technique"></a>La technique</h2><p>Le seul challenge que représente ce service est l’upload de très gros fichiers, on parle ici de plusieurs dizaines de gigaoctets. C’est donc sur ce point que je vais me concentrer.</p><p>Je ne compte pas passer mon été sur ce projet. Je prends donc un thème libre depuis <a href="https://html5up.net/">HTML5 Up</a> et je l’intègre à mon <a href="https://github.com/Buzut/frontend-boilerplate">outil de build pour sites statiques</a>. Ça me permet de profiter d’une bonne base, de booster un peu les perfs avec minification et pre-compression et d’ajouter ce dont je vais avoir besoin pour l’upload.</p><p>Côté back, rien de très complexe. Toute la vitrine du site est statique, reste plus qu’à mettre en place une micro API pour gérer les uploads, les conversions et les notifications. Bien entendu, c’est Node.js qui sera utilisé. La tâche est simple, je n’utilise aucun framework. Libpst sera bien entendu utilisée pour les conversions et les notifications seront envoyées par email, du classique !</p><p>Concernant l’upload, il est d’usage d’utiliser Resumable.js pour cette tâche. Je ne trouve pas la bibliothèque à mon goût pour de multiples raisons et puis il n’y a pas de module npm. On est en 2019, je ne me vois pas utiliser un truc qui nécessite que je l’intègre via <code>script src=&quot;…&quot;</code>.</p><p>Je décide donc de créer un module d’upload. Il devra gérer le chunking pour permettre l’upload de très gros fichiers, gérer les défaillances du réseau et permettre la mise en pause. Je créé en fait deux modules : <a href="https://github.com/Buzut/huge-uploader">huge uploader</a> est le module client dans le navigateur et <a href="https://github.com/Buzut/huge-uploader-nodejs">huge uploader nodejs</a> est, comme son nom l’indique, son pendant côté serveur.</p><p>Une fois l’upload terminé, l’archive est réassemblée sur le serveur et une crontask s’assurera de convertir l’archive PST en archive Mailbox. Lorsque c’est terminé, un email est envoyé au propriétaire de l’archive afin qu’il puisse la télécharger.</p><p>Enfin, 48h plus tard, tout est effacée car je n’ai ni l’usage ni la place de garder tout cela et qu’on est en Europe, RGPD ready baby.</p><h2 id="Business"><a href="#Business" class="headerlink" title="Business ?"></a>Business ?</h2><p>Comme je l’ai mentionné, j’avais simplement l’envie de créer un service face à un besoin perçu et pour le fun de coder une solution. Il est clair que ce n’est pas la prochaine licorne française.</p><p>Je fais simplement payer un coût fixe de 5€ pour les archives de plus de 100Mo histoire d’amortir le serveur, ni plus ni moins.</p>]]></content>
    
    <summary type="html">
    
      PSTconverter permet de convertir les archives PST en MBOX. Explications des technos derrière le service et sur le pourquoi de sa création.
    
    </summary>
    
    
    
      <category term="JavaScript" scheme="https://buzut.net/tag/JavaScript/"/>
    
      <category term="Actu" scheme="https://buzut.net/tag/Actu/"/>
    
  </entry>
  
</feed>
