<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Antoine Caron</title>
    <description>The latest articles on DEV Community by Antoine Caron (@slashgear_).</description>
    <link>https://dev.to/slashgear_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F149225%2F784d84fd-4648-416c-9a2c-b1863435f041.jpg</url>
      <title>DEV Community: Antoine Caron</title>
      <link>https://dev.to/slashgear_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/slashgear_"/>
    <language>en</language>
    <item>
      <title>Engineering manager : les métriques que je surveille</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Thu, 15 Oct 2026 17:18:10 +0000</pubDate>
      <link>https://dev.to/slashgear_/engineering-manager-les-metriques-que-je-surveille-m7k</link>
      <guid>https://dev.to/slashgear_/engineering-manager-les-metriques-que-je-surveille-m7k</guid>
      <description>&lt;p&gt;&lt;em&gt;Cet article a été publié initialement sur &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-metriques/" rel="noopener noreferrer"&gt;mon blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;C'est le quatrième article de ma série sur ce que le rôle d'engineering&lt;br&gt;
manager m'a appris. Comme pour les épisodes précédents, ce qui suit n'est ni&lt;br&gt;
une méthode ni une vérité générale : c'est un retour d'expérience personnel,&lt;br&gt;
structuré autour des citations et des principes que des personnes qui ont&lt;br&gt;
compté dans mon parcours m'ont transmis, et de ce qu'ils m'apportent au&lt;br&gt;
quotidien.&lt;/p&gt;

&lt;p&gt;Le premier épisode portait sur le &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-role-et-posture" rel="noopener noreferrer"&gt;rôle et la posture d'engineering&lt;br&gt;
manager&lt;/a&gt;, le deuxième sur &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-1-1-et-suivi-individuel" rel="noopener noreferrer"&gt;le&lt;br&gt;
1:1 et le suivi individuel&lt;/a&gt;,&lt;br&gt;
le troisième sur &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-objectifs-et-vision" rel="noopener noreferrer"&gt;donner des objectifs et de la&lt;br&gt;
vision&lt;/a&gt;. Vous pouvez retrouver&lt;br&gt;
tous les articles de la série sur la page du tag&lt;br&gt;
&lt;a href="https://blog.slashgear.dev/tags/engineering-management" rel="noopener noreferrer"&gt;engineering-management&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ce billet-ci se concentre sur les métriques que je surveille en tant&lt;br&gt;
qu'engineering manager : lesquelles j'utilise vraiment au quotidien, celles&lt;br&gt;
dont je me méfie, et la façon dont je les lis : pas comme des absolus, mais&lt;br&gt;
comme des tendances.&lt;/p&gt;

&lt;p&gt;N'hésitez pas à me proposer les métriques que vous suivez avec vos équipes :&lt;br&gt;
je serais curieux de découvrir comment chacun aborde le sujet. Mes réseaux&lt;br&gt;
sont accessibles depuis ce blog.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pourquoi je surveille des métriques
&lt;/h2&gt;

&lt;p&gt;Avant de parler des chiffres eux-mêmes, une précision sur la posture. Une&lt;br&gt;
métrique, ce n'est pas un tableau de bord qu'on brandit pour justifier une&lt;br&gt;
décision. C'est avant tout un instrument de calibrage : elle m'aide à&lt;br&gt;
comprendre où en est l'équipe, à détecter tôt un problème, et à savoir si mes&lt;br&gt;
choix de découpage ou de priorisation produisent l'effet attendu.&lt;/p&gt;

&lt;p&gt;J'avais déjà posé cette idée dans l'article précédent à propos du lead time :&lt;br&gt;
ce n'est pas un outil de flicage, c'est un instrument de calibrage. La même&lt;br&gt;
logique s'applique à toutes les métriques. Dès qu'un chiffre sert à juger une&lt;br&gt;
personne plutôt qu'à comprendre une situation, il devient toxique, et je&lt;br&gt;
reviendrai sur ce point à la fin.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ce qui ne veut pas dire grand-chose : la vélocité
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« La vélocité individuelle ou même la vélocité de l'équipe, ça veut pas&lt;br&gt;
dire grand-chose. » (&lt;a href="https://www.linkedin.com/in/florentdubost/" rel="noopener noreferrer"&gt;Florent Dubost&lt;/a&gt;,&lt;br&gt;
mon manager pendant plusieurs années)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;C'est une phrase que je me répète souvent, parce que la vélocité a tout pour&lt;br&gt;
plaire : elle se calcule facilement, elle se résume en un chiffre, et elle&lt;br&gt;
donne l'impression de mesurer « combien on produit ». Sauf que ça ne mesure&lt;br&gt;
en réalité presque rien.&lt;/p&gt;

&lt;p&gt;D'abord, comparer la vélocité de deux équipes n'a aucun sens : chaque équipe&lt;br&gt;
découpe, estime et compte ses points à sa façon. Ensuite, même au sein d'une&lt;br&gt;
même équipe, la vélocité agrège tout ce qui n'est pas comparable : un point&lt;br&gt;
n'est pas une valeur livrée. Une équipe peut très bien avoir une vélocité qui&lt;br&gt;
augmente pendant que la valeur délivrée, elle, plafonne ou régresse.&lt;/p&gt;

&lt;p&gt;La seule question qui m'intéresse, c'est : est-ce qu'on livre, en valeur,&lt;br&gt;
ce qu'on avait prévu de livrer ? La vélocité ne répond pas à cette question.&lt;br&gt;
C'est pour ça que je m'en méfie.&lt;/p&gt;

&lt;p&gt;Et il y a une autre raison, toute pratique : la vélocité est hyper sensible à&lt;br&gt;
la disponibilité de l'équipe et aux événements « extérieurs ». Une personne&lt;br&gt;
en vacances, un incident, une période d'onboarding, une semaine de&lt;br&gt;
conférences : tout ça fait chuter la vélocité sans qu'on ait rien changé à&lt;br&gt;
notre façon de travailler. Difficile, dans ces conditions, d'en tirer des&lt;br&gt;
analyses directes. À la limite, elle ne raconte que des variations, pas des&lt;br&gt;
causes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ce que je regarde vraiment : le cycle time
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Le throughput, franchement, pas trop. Le cycle time en médiane et en&lt;br&gt;
moyenne, oui, si je regarde. » (moi, en résumant ce que je regarde au&lt;br&gt;
quotidien)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si je devais ne garder qu'une famille de métriques, ce serait le temps. Le&lt;br&gt;
&lt;em&gt;lead time&lt;/em&gt; (déjà vu) mesure le temps entre l'idée et la livraison. Le &lt;em&gt;cycle&lt;br&gt;
time&lt;/em&gt;, lui, mesure le temps entre le début du travail sur un sujet et sa&lt;br&gt;
mise en production. Deux angles proches, mais le cycle time a l'avantage de&lt;br&gt;
commencer là où l'équipe a réellement la main.&lt;/p&gt;

&lt;p&gt;Le throughput, c'est-à-dire combien d'éléments on livre par unité de temps,&lt;br&gt;
je le regarde très peu. Il raconte un volume brut, sans rien dire de la valeur ni&lt;br&gt;
de la difficulté. À l'inverse, je surveille le cycle time, et je le regarde&lt;br&gt;
de deux façons complémentaires :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;la &lt;strong&gt;médiane&lt;/strong&gt;, qui me dit ce qui se passe pour le sujet « typique » de
l'équipe, sans être perturbée par les extrêmes ;&lt;/li&gt;
&lt;li&gt;la &lt;strong&gt;moyenne&lt;/strong&gt;, qui, elle, est sensible aux valeurs extrêmes : si elle
s'envole pendant que la médiane reste stable, c'est le signe qu'il existe
des sujets coincés quelque part, qui prennent un temps anormal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confronter les deux, c'est un réflexe simple qui en dit long : une médiane&lt;br&gt;
basse avec une moyenne haute, c'est la signature d'un backlog où quelques&lt;br&gt;
sujets traînent depuis des semaines pendant que le reste avance vite.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1l0llvyme61v0f4zmgpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1l0llvyme61v0f4zmgpt.png" alt="Comparaison entre médiane et moyenne du cycle time : la moyenne est tirée vers le haut par quelques sujets très lents, tandis que la médiane reste basse" width="800" height="369"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Quelques sujets très lents tirent la moyenne vers le haut, pendant que la médiane reste basse : la signature d'un backlog avec des sujets coincés.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Le vrai temps de release : deploy + rollback
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Ok tu déploies en 10 min mais il te faut combien de temps pour rollback ?&lt;br&gt;
C'est automatisé ? » (&lt;a href="https://www.linkedin.com/in/kenny-d-3761b59b/" rel="noopener noreferrer"&gt;Kenny Dits&lt;/a&gt;,&lt;br&gt;
mon chef)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cette question de Kenny m'a marqué, parce qu'elle déplace le vrai sujet. On a&lt;br&gt;
tous tendance à juger une livraison sur la rapidité du déploiement : « on&lt;br&gt;
déploie en dix minutes ». Sauf que le temps de release ne se limite pas au&lt;br&gt;
deploy. Il faut y ajouter tout ce qu'il faut pour revenir en arrière si&lt;br&gt;
quelque chose ne va pas : le rollback.&lt;/p&gt;

&lt;p&gt;Un déploiement en dix minutes sans rollback automatisé, c'est un déploiement&lt;br&gt;
qui peut vous coûter une soirée entière. C'est cette logique qu'il faut&lt;br&gt;
intégrer : &lt;strong&gt;le vrai temps de release, c'est deploy + rollback.&lt;/strong&gt; Et un&lt;br&gt;
rollback n'est un vrai filet de sécurité que s'il est automatisé, au même&lt;br&gt;
titre que le deploy, et pas une procédure manuelle qu'on redécouvre dans&lt;br&gt;
l'urgence.&lt;/p&gt;

&lt;p&gt;J'en tire une règle simple que j'applique aux créneaux de mise en&lt;br&gt;
production : si quelqu'un de l'équipe libère à 18 h 30 et que le temps total&lt;br&gt;
(deploy + rollback) est d'une heure, alors il faut s'attendre à pouvoir être&lt;br&gt;
occupé jusqu'à 19 h 30. Autrement dit, une release, on la place en&lt;br&gt;
connaissant le pire cas, pas le meilleur.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0v3rs3exu3f4h946oyip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0v3rs3exu3f4h946oyip.png" alt="Le temps de release se compose du deploy et du rollback ; une release à 18h30 avec une heure de temps total engage jusqu'à 19h30" width="800" height="385"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;On place une release en connaissant le pire cas (deploy plus rollback), et non en se fiant au temps de deploy seul.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Le deploy frequency et mon rapport au déploiement continu
&lt;/h2&gt;

&lt;p&gt;Des métriques DORA&lt;sup&gt;1&lt;/sup&gt;, celle que j'exploite principalement, c'est la&lt;br&gt;
&lt;em&gt;deploy frequency&lt;/em&gt; : la fréquence à laquelle on met en production. C'est la&lt;br&gt;
plus parlante pour l'équipe, parce qu'elle raconte directement le rythme de&lt;br&gt;
livraison, celui qu'on ressent au quotidien.&lt;/p&gt;

&lt;p&gt;Cela dit, je n'applique pas le modèle « déploiement continu à chaque merge&lt;br&gt;
sur la branche principale » de façon dogmatique. C'est un très bon objectif&lt;br&gt;
dans beaucoup d'équipes, mais ce n'est pas une fin en soi. Ma préférence, et&lt;br&gt;
je l'assume, c'est plutôt un &lt;strong&gt;déploiement régulier à chaque &lt;em&gt;Mesure&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
(cette unité de temps dont je parlais dans l'article précédent), après une&lt;br&gt;
&lt;strong&gt;démo aux stakeholders&lt;/strong&gt; pour valider les développements.&lt;/p&gt;

&lt;p&gt;Concrètement : on développe pendant la &lt;em&gt;Mesure&lt;/em&gt;, on présente ce qui est prêt&lt;br&gt;
aux parties prenantes, et c'est après cette validation qu'on déploie. La&lt;br&gt;
fréquence est donc régulière et prévisible, mais elle est pilotée par un&lt;br&gt;
moment de validation humaine plutôt que par chaque merge. Ça colle mieux à la&lt;br&gt;
façon dont je veux que l'équipe avance : livrer souvent, mais en sachant ce&lt;br&gt;
qu'on livre et pourquoi.&lt;/p&gt;

&lt;p&gt;Ce choix a aussi un effet d'entraînement sur les parties prenantes. Un&lt;br&gt;
déploiement régulier, à un rythme connu, habitue tout le monde au fait que ce&lt;br&gt;
rythme existe, et crée une forme de confiance dans le « ça sort à la prochaine&lt;br&gt;
&lt;em&gt;Mesure&lt;/em&gt; ». Ça prendra quelques mois, mais ce rythme finit par être compris&lt;br&gt;
par l'équipe et autour de l'équipe, et surtout, il évite les sempiternelles&lt;br&gt;
questions du « quand est-ce que ça sort ? ». On sait, et on le sait ensemble.&lt;/p&gt;

&lt;p&gt;Enfin, déployer souvent présente deux avantages que je ne veux pas perdre de&lt;br&gt;
vue. D'abord, cela permet de détecter les régressions plus vite : moins il y&lt;br&gt;
a d'écart entre ce qu'on livre et la production, plus il est facile de&lt;br&gt;
remonter à l'origine d'un problème. Ensuite, cela engage moins à chaque&lt;br&gt;
release, tout simplement parce qu'on lance moins de code en production à&lt;br&gt;
chaque fois. Le rythme régulier à chaque &lt;em&gt;Mesure&lt;/em&gt; n'est donc pas en&lt;br&gt;
contradiction avec ces bénéfices : il les garde, tout en introduisant le&lt;br&gt;
moment de validation qui me semble indispensable.&lt;/p&gt;

&lt;p&gt;Pour fixer ce rythme, une mise en garde : plutôt que de vous donner un&lt;br&gt;
objectif non SMART du type « déployer plus souvent », donnez-vous un rythme&lt;br&gt;
précis et soutenable, et tenez-vous y. Un rythme qu'on tient sur la durée&lt;br&gt;
vaut toujours mieux qu'une ambition qu'on abandonne au bout de deux&lt;br&gt;
semaines.&lt;/p&gt;

&lt;p&gt;Et quand vous voulez améliorer le temps de déploiement, pensez à toute la&lt;br&gt;
chaîne deploy + rollback, pas seulement au geste de déploiement en lui-même.&lt;br&gt;
Identifiez l'étape qui prend le plus de temps et attaquez-vous à elle en&lt;br&gt;
priorité : c'est là que vous gagnerez le plus. Pour donner un ordre de&lt;br&gt;
repère, un temps de deploy + rollback viable, pour moi, c'est moins de&lt;br&gt;
trente minutes.&lt;/p&gt;
&lt;h2&gt;
  
  
  La rétro radar, pour suivre le qualitatif
&lt;/h2&gt;

&lt;p&gt;Les métriques techniques disent beaucoup du rythme et de la stabilité, mais&lt;br&gt;
elles sont muettes sur l'essentiel : comment les gens se sentent dans&lt;br&gt;
l'équipe. C'est là qu'intervient la &lt;strong&gt;rétro radar&lt;/strong&gt;, le format de&lt;br&gt;
&lt;a href="https://www.neatro.io/fr/blog/retrospective-team-radar/" rel="noopener noreferrer"&gt;rétrospective en radar&lt;/a&gt;&lt;br&gt;
que &lt;a href="https://www.linkedin.com/in/caroline-le-lay-leme/" rel="noopener noreferrer"&gt;Caroline Le Lay&lt;/a&gt;, une&lt;br&gt;
amie et ancienne collègue, m'a fait découvrir. Je la fais environ tous les&lt;br&gt;
six mois, et je la trouve très intéressante pour mesurer l'évolution&lt;br&gt;
qualitative et humaine de l'équipe.&lt;/p&gt;

&lt;p&gt;Le principe est simple. On définit des axes, chacun représentant une&lt;br&gt;
dimension de la vie d'équipe. Chez moi, ils sont dix : collaboration, fun,&lt;br&gt;
apprentissages, mission, ownership, process, ressources, rôles, vitesse et&lt;br&gt;
valeur. À chaque itération, chaque membre se positionne sur ces axes, et on&lt;br&gt;
reporte les résultats sur un radar : plus on est loin du centre, plus la&lt;br&gt;
dimension est au vert.&lt;/p&gt;

&lt;p&gt;Chaque axe correspond à une question précise sur la santé de l'équipe :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axe&lt;/th&gt;
&lt;th&gt;Ce que ça mesure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Collaboration&lt;/td&gt;
&lt;td&gt;Une communication constante et de qualité, respectueuse.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fun&lt;/td&gt;
&lt;td&gt;Une ambiance agréable, le plaisir à travailler ensemble.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apprentissages&lt;/td&gt;
&lt;td&gt;Des compétences qui progressent, itération après itération.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mission&lt;/td&gt;
&lt;td&gt;L'alignement sur les objectifs de l'entreprise et la création de valeur.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ownership&lt;/td&gt;
&lt;td&gt;L'autonomie, la capacité à décider par soi-même.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process&lt;/td&gt;
&lt;td&gt;Des processus qui aident à créer de la valeur, sans ralentir ni bloquer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ressources&lt;/td&gt;
&lt;td&gt;L'accès aux moyens matériels et au support nécessaires.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rôles&lt;/td&gt;
&lt;td&gt;Des rôles et responsabilités clairs, toutes les compétences présentes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vitesse&lt;/td&gt;
&lt;td&gt;De la valeur produite en respectant les dates, à un rythme sain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valeur&lt;/td&gt;
&lt;td&gt;Une valeur mesurable, quantifiable et réalisable par l'équipe.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Un détail pratique qui change tout : j'envoie le formulaire à l'avance. Si&lt;br&gt;
chacun remplit son positionnement avant la séance, la rétro elle-même ne sert&lt;br&gt;
plus à collecter des réponses, mais uniquement à échanger, à prendre des&lt;br&gt;
notes sur les résultats, et à définir ensemble des actions. On gagne un temps&lt;br&gt;
précieux et on garde la séance pour ce qui compte : la discussion et les&lt;br&gt;
décisions, pas la saisie.&lt;/p&gt;

&lt;p&gt;L'intérêt n'est pas tant la forme du radar à un instant t que son &lt;strong&gt;évolution&lt;br&gt;
d'une itération à l'autre&lt;/strong&gt;. On superpose le radar de la fois précédente et&lt;br&gt;
celui du jour : ce qui s'agrandit, ce qui se rétracte, ce qui se dégrade&lt;br&gt;
sans qu'on l'ait vu venir. C'est un excellent détecteur précoce : une&lt;br&gt;
dimension qui se dégrade discrètement pendant deux itérations mérite qu'on&lt;br&gt;
s'y attarde avant que ça ne devienne un problème.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft39e0kf620n8i13692tx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft39e0kf620n8i13692tx.png" alt="Un radar à dix axes (collaboration, fun, apprentissages, mission, ownership, process, ressources, rôles, vitesse, valeur) : le polygone de l'itération précédente en pointillés, celui de l'itération actuelle en couleur, montrant l'évolution" width="800" height="529"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;On ne lit pas le radar comme un absolu, mais comme une évolution d'une itération à l'autre.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Un squelette d'axes que vous pouvez adapter :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Rétro radar · Mesure du AAAA-MM-JJ&lt;/span&gt;

| Axe            | Position (1 à 5) | Commentaire |
| -------------- | ---------------- | ----------- |
| Collaboration  | …                | …           |
| Fun            | …                | …           |
| Apprentissages | …                | …           |
| Mission        | …                | …           |
| Ownership      | …                | …           |
| Process        | …                | …           |
| Ressources     | …                | …           |
| Rôles          | …                | …           |
| Vitesse        | …                | …           |
| Valeur         | …                | …           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comme pour le journal de bord et le brag document vus dans le deuxième&lt;br&gt;
article, l'important est de consigner : c'est en superposant les relevés&lt;br&gt;
successifs qu'on voit l'évolution, pas en se fiant à la mémoire de « ce&lt;br&gt;
qu'on ressentait la dernière fois ».&lt;/p&gt;

&lt;h2&gt;
  
  
  Regarder les tendances, pas les absolus
&lt;/h2&gt;

&lt;p&gt;Une dernière chose, peut-être la plus importante. Ce que j'évite avec les&lt;br&gt;
métriques, c'est de les regarder comme des absolus. Un chiffre isolé ne veut&lt;br&gt;
pas dire grand-chose : « notre cycle time est de cinq jours » n'a de sens&lt;br&gt;
que comparé à ce qu'il était il y a un mois, ou à ce qu'on s'était fixé.&lt;/p&gt;

&lt;p&gt;Je regarde donc surtout les &lt;strong&gt;tendances et les évolutions&lt;/strong&gt;. Est-ce que ça&lt;br&gt;
s'améliore, est-ce que ça se dégrade, est-ce que ça stagne ? C'est la pente&lt;br&gt;
qui m'intéresse, pas la valeur. Une métrique qui se dégrade légèrement mais&lt;br&gt;
dont on connaît la cause est bien moins inquiétante qu'un chiffre stable&lt;br&gt;
dont plus personne ne se demande pourquoi il est ce qu'il est.&lt;/p&gt;

&lt;p&gt;Et surtout, une métrique ne doit jamais devenir une cible à atteindre à&lt;br&gt;
tout prix : sinon elle cesse de mesurer la réalité et commence à la&lt;br&gt;
déformer&lt;sup&gt;2&lt;/sup&gt;. Le jour où un chiffre devient un jugement sur les&lt;br&gt;
personnes plutôt qu'un instrument de compréhension, il faut savoir le&lt;br&gt;
ranger. C'est le meilleur garde-fou que je connaisse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Et la suite ?
&lt;/h2&gt;

&lt;p&gt;Il reste d'autres aspects des métriques que je n'ai pas abordés ici. Si vous&lt;br&gt;
voulez que je précise un point ou que j'aborde un thème en particulier, mes&lt;br&gt;
réseaux sont accessibles depuis ce blog : écrivez-moi, je serai ravi de vous&lt;br&gt;
lire.&lt;/p&gt;

&lt;p&gt;Le prochain épisode portera sur &lt;strong&gt;le recrutement, l'onboarding et&lt;br&gt;
l'offboarding&lt;/strong&gt;, un sujet que j'ai déjà évoqué en passant dans le deuxième&lt;br&gt;
article. Comme toujours, vous pouvez retrouver l'ensemble de la série via le&lt;br&gt;
tag &lt;a href="https://blog.slashgear.dev/tags/engineering-management" rel="noopener noreferrer"&gt;engineering-management&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Les métriques DORA (&lt;em&gt;DevOps Research and Assessment&lt;/em&gt;) regroupent quatre indicateurs de performance : la deploy frequency, le lead time, la change failure rate et le mean time to recovery. Stéphane Robert les présente très bien sur son blog : &lt;a href="https://blog.stephane-robert.info/docs/devops/fondamentaux/dora/" rel="noopener noreferrer"&gt;&lt;em&gt;DORA, les métriques devops&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;C'est la &lt;a href="https://fr.wikipedia.org/wiki/Loi_de_Goodhart" rel="noopener noreferrer"&gt;loi de Goodhart&lt;/a&gt; : quand une mesure devient une cible, elle cesse d'être une bonne mesure.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
      <category>french</category>
    </item>
    <item>
      <title>Engineering manager : donner des objectifs et de la vision</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Thu, 15 Oct 2026 17:15:26 +0000</pubDate>
      <link>https://dev.to/slashgear_/engineering-manager-donner-des-objectifs-et-de-la-vision-29ep</link>
      <guid>https://dev.to/slashgear_/engineering-manager-donner-des-objectifs-et-de-la-vision-29ep</guid>
      <description>&lt;p&gt;&lt;em&gt;Cet article a été publié initialement sur &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-objectifs-et-vision/" rel="noopener noreferrer"&gt;mon blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;C'est le troisième article de ma série sur ce que le rôle d'engineering&lt;br&gt;
manager m'a appris. Comme pour les épisodes précédents, ce qui suit n'est&lt;br&gt;
ni une méthode ni une vérité générale : c'est un retour d'expérience&lt;br&gt;
personnel, structuré autour des citations et des principes que des&lt;br&gt;
personnes qui ont compté dans mon parcours m'ont transmis, et de ce qu'ils&lt;br&gt;
m'apportent au quotidien.&lt;/p&gt;

&lt;p&gt;Le premier épisode portait sur le &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-role-et-posture" rel="noopener noreferrer"&gt;rôle et la posture d'engineering&lt;br&gt;
manager&lt;/a&gt;, le deuxième sur &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-1-1-et-suivi-individuel" rel="noopener noreferrer"&gt;le&lt;br&gt;
1:1 et le suivi individuel&lt;/a&gt;.&lt;br&gt;
Vous pouvez retrouver tous les articles de la série sur la page du tag&lt;br&gt;
&lt;a href="https://blog.slashgear.dev/tags/engineering-management" rel="noopener noreferrer"&gt;engineering-management&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ce billet-ci se concentre sur les objectifs et la vision : comment se&lt;br&gt;
donner des horizons de travail, comment les faire vivre dans le temps, et&lt;br&gt;
comment les rendre compréhensibles par toute l'équipe.&lt;/p&gt;
&lt;h2&gt;
  
  
  Se donner des horizons
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Un auteur sans deadline, c'est juste un mec au chômage. » (Kyan&lt;br&gt;
Khojandi, entendu dans une interview)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il existe, pour moi, trois types de projets : ceux drivés par une date,&lt;br&gt;
ceux drivés par un résultat attendu, et ceux drivés par les deux à la fois.&lt;br&gt;
Ce troisième cas est de loin le plus inconfortable : si vous savez&lt;br&gt;
exactement ce que vous voulez livrer, il devient très compliqué d'imposer&lt;br&gt;
en plus une date fixe, sans risquer soit le retard, soit un scope amputé&lt;br&gt;
pour tenir les délais.&lt;/p&gt;

&lt;p&gt;La question à garder en tête en permanence, c'est : est-ce que l'important&lt;br&gt;
c'est que ça sorte à telle date, quitte à ce que le scope bouge ? Ou est-ce&lt;br&gt;
que l'important c'est ce qui doit sortir, quitte à ce que la date bouge ?&lt;br&gt;
&lt;strong&gt;Un projet sans réponse claire à cette question navigue à vue.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se donner des dates et des jalons, ce n'est pas de la rigidité gratuite :&lt;br&gt;
c'est se forcer à se demander régulièrement « comment je peux rapidement&lt;br&gt;
délivrer un truc qui marche, que je peux montrer ? ». On touche là au cœur&lt;br&gt;
de l'agilité et de la création de valeur. Et l'inverse est tout aussi vrai&lt;br&gt;
: si vous n'avez pas d'ambition de date de fin pour un projet, ne soyez pas&lt;br&gt;
surpris s'il ne finit jamais. C'est la loi de Parkinson : un travail&lt;br&gt;
s'étend jusqu'à occuper tout le temps disponible pour son achèvement.&lt;/p&gt;

&lt;p&gt;Pour fixer ces horizons, je recommande de s'aligner sur le calendrier de&lt;br&gt;
l'entreprise. Si elle revoit ses objectifs tous les six mois, construire&lt;br&gt;
des projets qui tiennent sur six mois est une bonne idée ; pareil pour&lt;br&gt;
trois mois, ou pour quatre semaines. Appelons ça, comme en musique, la&lt;br&gt;
&lt;em&gt;Mesure&lt;/em&gt;&lt;sup&gt;1&lt;/sup&gt; : l'unité de temps qui rythme le travail de l'équipe. La&lt;br&gt;
question qui en découle, à se poser à chaque nouvelle &lt;em&gt;Mesure&lt;/em&gt; : « qu'est-ce&lt;br&gt;
qu'on peut faire de mieux pour répondre à ce problème en X semaines ? »&lt;/p&gt;
&lt;h2&gt;
  
  
  La macula : net de près, flou de loin
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Construire une roadmap sur six mois ou un an, ce n'est pas savoir&lt;br&gt;
exactement ce qu'on fera dans six mois ou un an, c'est définir ce qu'on&lt;br&gt;
fait demain et dans quelle direction on va. » (plusieurs coachs Agile&lt;br&gt;
avec qui j'ai travaillé)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;C'est l'image que je préfère pour parler de roadmap : celle de la macula,&lt;br&gt;
cette petite zone au centre de la rétine qui capte le détail net, entourée&lt;br&gt;
d'une vision périphérique bien plus floue. D'abord avoir une vision claire&lt;br&gt;
du mois prochain, avant d'essayer de savoir précisément ce qu'on fera dans&lt;br&gt;
six mois. &lt;strong&gt;Se laisser de la place sur le moyen et le long terme, plutôt&lt;br&gt;
que de prétendre à une précision qu'on n'a pas.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhruspx6r6si5bsiuerm3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhruspx6r6si5bsiuerm3.png" alt="Une ligne temporelle où la netteté décroît de gauche à droite : demain est net et détaillé, le mois prochain un peu moins, et l'horizon à six mois-un an n'est plus qu'une direction floue" width="800" height="338"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Comme la vision humaine : net au centre, de plus en plus flou vers la périphérie. La roadmap fonctionne pareil.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Expliquer ce qu'on va faire est important, mais exprimer pourquoi on va le&lt;br&gt;
faire, en quoi c'est nécessaire, est un besoin tout aussi fort — j'y&lt;br&gt;
reviens dans la section sur la répétition, un peu plus loin.&lt;/p&gt;

&lt;p&gt;Il y a un moment où cette netteté se rejoue : la bascule d'une &lt;em&gt;Mesure&lt;/em&gt; à la&lt;br&gt;
suivante. C'est souvent là qu'il faut consacrer plus de temps que&lt;br&gt;
d'habitude, en deux temps. D'abord faire les comptes de la &lt;em&gt;Mesure&lt;/em&gt; qui se&lt;br&gt;
termine : est-ce que les objectifs sont atteints, est-ce qu'on a fini ce&lt;br&gt;
qu'on voulait faire. Ensuite regarder ce qu'on veut faire dans la&lt;br&gt;
suivante, et le valider avec l'équipe. On ne bascule pas une &lt;em&gt;Mesure&lt;/em&gt; en&lt;br&gt;
mode automatique ; on la clôt, puis on en ouvre une nouvelle.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mesurer le lead time
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Ça fait quatre ans qu'on parle de ce projet, donc on va pas parler de&lt;br&gt;
lead time. » (un ancien collègue, anonyme — la formule fait toujours&lt;br&gt;
mouche)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le lead time, c'est le temps écoulé entre le moment où un sujet est&lt;br&gt;
identifié et le moment où il est réellement livré. C'est une mesure&lt;br&gt;
implacable : elle ne s'intéresse pas à combien de temps vous avez&lt;br&gt;
« vraiment » travaillé dessus, seulement à combien de temps il a fallu à&lt;br&gt;
l'idée pour devenir réalité.&lt;/p&gt;

&lt;p&gt;Je tiens, de mon côté, une petite liste personnelle où je note et je&lt;br&gt;
mesure le lead time des projets de mon équipe. &lt;strong&gt;Ce n'est pas un outil de&lt;br&gt;
flicage : c'est un instrument de calibrage.&lt;/strong&gt; Mon objectif en tant&lt;br&gt;
qu'engineering manager, c'est que la médiane des lead time de mon équipe&lt;br&gt;
reste inférieure à la &lt;em&gt;Mesure&lt;/em&gt; de temps de l'entreprise vue plus haut. Si&lt;br&gt;
l'entreprise raisonne à six mois et que la médiane de mes projets dépasse&lt;br&gt;
six mois, quelque chose ne va pas dans la façon dont je les découpe ou dont&lt;br&gt;
je les priorise — et la citation ci-dessus finit tôt ou tard par devenir&lt;br&gt;
vraie dans ma propre équipe.&lt;/p&gt;
&lt;h2&gt;
  
  
  Répéter et multiplier les formats
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Monsieur, qu'est-ce qu'il faut faire déjà ? » (mes étudiants, à&lt;br&gt;
l'époque où je donnais des cours à la fac)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Donner des cours m'a appris quelque chose que j'ai réemployé sans arrêt en&lt;br&gt;
tant que manager : &lt;strong&gt;pour qu'une information soit transmise et vraiment&lt;br&gt;
comprise, il faut la répéter, et la répéter sous des formats&lt;br&gt;
différents.&lt;/strong&gt; Une seule annonce, un seul support, ne suffit jamais — même&lt;br&gt;
quand on a l'impression d'avoir été parfaitement clair la première fois.&lt;/p&gt;

&lt;p&gt;Concrètement, pour la vision et les objectifs, je fais vivre la même&lt;br&gt;
information sous plusieurs formes :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;une réunion en fin de &lt;em&gt;Mesure&lt;/em&gt; pour présenter et discuter la suivante ;&lt;/li&gt;
&lt;li&gt;en 1:1, une question que je pose régulièrement à chacun : « est-ce que tu
vois où on va ? est-ce qu'il y a un projet, chez nous, qui te semble pas
clair ? » ;&lt;/li&gt;
&lt;li&gt;la roadmap elle-même, partagée et mise à jour régulièrement, au format
image — un visuel qu'on peut regarder d'un coup d'œil vaut mieux qu'un
document qu'il faut rouvrir pour se souvenir où on en est.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Petite parenthèse : c'est en cherchant justement ce format visuel, simple&lt;br&gt;
à tenir à jour, que j'ai fini par construire mon propre petit outil de&lt;br&gt;
roadmap, disponible ici : &lt;a href="https://roadmap.slashgear.dev/" rel="noopener noreferrer"&gt;roadmap.slashgear.dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcknbsfgoexvo52epdi6t.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcknbsfgoexvo52epdi6t.webp" alt="Exemple de roadmap générée avec roadmap.slashgear.dev : une section « This month » dense et confirmée, puis « Next quarter » avec moins de détail, puis « H2 direction » réduite à une seule barre floue « scope TBD »" width="800" height="737"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vous remarquerez que cet exemple applique lui-même le principe de la&lt;br&gt;
macula vu plus haut : beaucoup de détail confirmé sur le mois en cours,&lt;br&gt;
un peu moins sur le trimestre suivant, et un unique bandeau flou « scope&lt;br&gt;
TBD » pour le second semestre.&lt;/p&gt;
&lt;h2&gt;
  
  
  SMART, et le piège du silo
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Un objectif malin, c'est SMART. »&lt;br&gt;
(&lt;a href="https://www.linkedin.com/in/caroline-le-lay-leme/" rel="noopener noreferrer"&gt;Caroline Le Lay&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Un petit rappel s'impose : un objectif &lt;a href="https://fr.wikipedia.org/wiki/Objectifs_et_indicateurs_SMART" rel="noopener noreferrer"&gt;SMART&lt;/a&gt; est &lt;strong&gt;S&lt;/strong&gt;pécifique,&lt;br&gt;
&lt;strong&gt;M&lt;/strong&gt;esurable, &lt;strong&gt;A&lt;/strong&gt;tteignable, &lt;strong&gt;R&lt;/strong&gt;éaliste (ou pertinent) et &lt;strong&gt;T&lt;/strong&gt;emporel.&lt;br&gt;
« Améliorer la qualité » n'est pas un objectif SMART ; « réduire de 30 % le&lt;br&gt;
nombre de tickets de support ouverts par mois d'ici la fin de la &lt;em&gt;Mesure&lt;/em&gt; »&lt;br&gt;
en est un. La différence, c'est qu'on sait, sans ambiguïté possible, quand&lt;br&gt;
il est atteint.&lt;/p&gt;

&lt;p&gt;Un exemple vécu : quelqu'un dans l'équipe se plaint de porter trop de&lt;br&gt;
tickets de bug tout seul, et aimerait que ça se répartisse mieux.&lt;br&gt;
L'équipe se fixe alors comme objectif de « mieux répartir les tickets de&lt;br&gt;
bug ». Pas très SMART : comment sait-on, concrètement, qu'on l'a atteint ?&lt;/p&gt;

&lt;p&gt;Une première piste pour le rendre SMART : que chacun traite au moins un&lt;br&gt;
ticket de bug par &lt;em&gt;Mesure&lt;/em&gt;. Juste un ? Ça paraît dérisoire, dit comme ça.&lt;br&gt;
Mais si la situation de départ, c'est zéro ticket de bug traité par la&lt;br&gt;
moitié de l'équipe, passer de zéro à un pour tout le monde, franchement,&lt;br&gt;
ça paraît déjà pas mal, non ? C'est un objectif atteignable, sur lequel&lt;br&gt;
personne ne peut dire « ça ne me concerne pas » — et rien n'empêche de&lt;br&gt;
relever la barre à la &lt;em&gt;Mesure&lt;/em&gt; suivante une fois que ce premier palier&lt;br&gt;
est acquis.&lt;/p&gt;

&lt;p&gt;Mais définir un objectif SMART ne dit rien de qui le porte, et c'est là que&lt;br&gt;
se cache le vrai piège. Je préfère, autant que possible, donner des&lt;br&gt;
objectifs à l'équipe plutôt que des objectifs individuels. Un objectif&lt;br&gt;
individuel a tendance à siloter le travail : chacun défend son bout de&lt;br&gt;
périmètre, et l'entraide en pâtit, parfois sans même qu'on s'en rende&lt;br&gt;
compte. Un objectif d'équipe pousse au contraire tout le monde à avancer&lt;br&gt;
ensemble, à s'entraider pour l'atteindre, plutôt qu'à optimiser&lt;br&gt;
chacun dans son coin.&lt;/p&gt;

&lt;p&gt;Concrètement, sur un même sujet, ça peut donner deux objectifs tout aussi&lt;br&gt;
SMART l'un que l'autre, mais avec des effets opposés :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;À éviter — objectif individuel :&lt;/strong&gt; « Chaque développeur ferme au
moins 15 tickets de support par &lt;em&gt;Mesure&lt;/em&gt;. » Chacun va naturellement
piocher les tickets faciles pour faire son quota ; le ticket compliqué
qui traîne depuis trois semaines, personne n'y touche, puisqu'il ne
rapporte rien à personne en particulier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;À privilégier — objectif d'équipe :&lt;/strong&gt; « L'équipe réduit de 30 % le
nombre de tickets de support ouverts par mois d'ici la fin de la
&lt;em&gt;Mesure&lt;/em&gt;. » Peu importe qui ferme quoi : seul le chiffre global compte,
donc s'entraider sur le ticket compliqué devient rationnel plutôt que
perçu comme du temps perdu.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Même chose sur un projet plus large :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;À éviter — objectif individuel :&lt;/strong&gt; « Le lead technique livre la
migration vers la nouvelle API de paiement. » Un seul point de
défaillance : si cette personne part en vacances, change d'équipe ou
tombe malade, le projet s'arrête net, et personne d'autre n'a le
contexte pour reprendre.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;À privilégier — objectif d'équipe :&lt;/strong&gt; « L'équipe a basculé 100 % du
trafic de paiement vers la nouvelle API d'ici la fin du trimestre. »
N'importe qui peut reprendre la suite, parce que le contexte est
partagé dès le départ plutôt que concentré dans une seule tête.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Pas de ticket pour dans six mois
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Écrire un ticket pour un sujet potentiel à faire dans six mois, c'est&lt;br&gt;
globalement toujours une mauvaise idée. »&lt;br&gt;
(&lt;a href="https://www.linkedin.com/in/emmanuelherve/" rel="noopener noreferrer"&gt;Emmanuel Hervé&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le coût de maintenance d'un ticket ouvert pour dans six mois est énorme,&lt;br&gt;
même à l'ère de l'IA qui promet d'en absorber une partie. On finit avec un&lt;br&gt;
backlog de 600 tickets, dont certains ont deux ou trois ans, et dont plus&lt;br&gt;
personne ne connaît vraiment le détail. &lt;strong&gt;Un backlog de 2 000 tickets&lt;br&gt;
n'est pas une roadmap : c'est un cimetière d'intentions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si un sujet est vraiment important, il reviendra. Pas besoin de lui&lt;br&gt;
réserver une place dans le backlog pour ça. Ce que je fais à la place :&lt;br&gt;
noter l'idée dans une page « boîte à idées », pas dans un vrai ticket. Ça&lt;br&gt;
évite le coût de maintenance, tout en gardant une trace suffisante pour la&lt;br&gt;
retrouver le jour où le sujet redevient d'actualité.&lt;/p&gt;

&lt;p&gt;Construire une roadmap, ça demande justement de s'extraire des sujets du&lt;br&gt;
quotidien pour aller enquêter sur les véritables enjeux de l'entreprise.&lt;br&gt;
Et côté technique, c'est aussi accepter de trancher : décider sur quoi on&lt;br&gt;
choisit d'investir, et donc, implicitement, sur quoi on choisit de ne pas&lt;br&gt;
investir tout de suite.&lt;/p&gt;

&lt;p&gt;Un squelette de boîte à idées que vous pouvez copier tel quel :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Boîte à idées&lt;/span&gt;

| Idée | Notée le   | Contexte | Statut                              |
| ---- | ---------- | -------- | ----------------------------------- |
| …    | AAAA-MM-JJ | …        | à explorer / abandonnée / en Mesure |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Et la suite ?
&lt;/h2&gt;

&lt;p&gt;Il reste d'autres aspects des objectifs et de la vision que je n'ai pas&lt;br&gt;
abordés ici. Si vous voulez que je précise un point ou que j'aborde un&lt;br&gt;
thème en particulier, mes réseaux sont accessibles depuis ce blog :&lt;br&gt;
écrivez-moi, je serai ravi de vous lire.&lt;/p&gt;

&lt;p&gt;Le lead time n'est qu'une métrique parmi d'autres : le prochain épisode&lt;br&gt;
portera sur &lt;strong&gt;les métriques que je surveille en tant qu'engineering&lt;br&gt;
manager&lt;/strong&gt;, pour aller plus loin. Comme toujours, vous pouvez retrouver&lt;br&gt;
l'ensemble de la série via le tag&lt;br&gt;
&lt;a href="https://blog.slashgear.dev/tags/engineering-management" rel="noopener noreferrer"&gt;engineering-management&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Mesure&lt;/em&gt; : terme que j'emploie ici comme en musique, pour désigner l'unité de temps qui se répète et rythme le travail de l'équipe (un mois, un trimestre, six mois — selon le calendrier de l'entreprise). Une &lt;em&gt;Mesure&lt;/em&gt; n'est pas un sprint : c'est l'horizon sur lequel se fixent les objectifs, avant de faire les comptes et d'en ouvrir une nouvelle.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
      <category>french</category>
    </item>
    <item>
      <title>Engineering manager : le 1:1 et le suivi individuel</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Thu, 15 Oct 2026 17:11:47 +0000</pubDate>
      <link>https://dev.to/slashgear_/engineering-manager-le-11-et-le-suivi-individuel-2cno</link>
      <guid>https://dev.to/slashgear_/engineering-manager-le-11-et-le-suivi-individuel-2cno</guid>
      <description>&lt;p&gt;&lt;em&gt;Cet article a été publié initialement sur &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-1-1-et-suivi-individuel/" rel="noopener noreferrer"&gt;mon blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;C'est le deuxième article de ma série sur ce que le rôle d'engineering&lt;br&gt;
manager m'a appris. Comme pour le premier épisode, ce qui suit n'est ni une&lt;br&gt;
méthode ni une vérité générale : c'est un retour d'expérience personnel,&lt;br&gt;
structuré autour des citations et des principes que des personnes qui ont&lt;br&gt;
compté dans mon parcours m'ont transmis, et de ce qu'ils m'apportent au&lt;br&gt;
quotidien.&lt;/p&gt;

&lt;p&gt;Le premier épisode portait sur le &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-role-et-posture" rel="noopener noreferrer"&gt;rôle et la posture&lt;br&gt;
d'engineering manager&lt;/a&gt; ; si vous&lt;br&gt;
ne l'avez pas lu, c'est une bonne entrée en matière avant celui-ci. Vous&lt;br&gt;
pouvez aussi retrouver tous les articles de la série sur la page du tag&lt;br&gt;
&lt;a href="https://blog.slashgear.dev/tags/engineering-management" rel="noopener noreferrer"&gt;engineering-management&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ce billet-ci se concentre sur le 1:1 et le suivi individuel : ce temps&lt;br&gt;
dédié, régulier, en tête-à-tête avec chaque personne de l'équipe, et tout ce&lt;br&gt;
qui se joue autour pour accompagner les individus dans la durée.&lt;/p&gt;
&lt;h2&gt;
  
  
  Le paradoxe de la porte ouverte
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Ma porte est toujours ouverte. » (une phrase que j'ai entendue de la&lt;br&gt;
bouche de plusieurs managers, que je ne citerai pas ici)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Annoncer que sa porte est ouverte, ce n'est pas si généreux qu'il n'y&lt;br&gt;
paraît. Au fond, cela ne fait qu'ériger en principe le « venez me déranger&lt;br&gt;
quand vous en avez besoin » : la charge de provoquer l'échange repose&lt;br&gt;
entièrement sur le managé. Dans une petite équipe, ça peut suffire. Passé&lt;br&gt;
une certaine taille, ça ne tient plus.&lt;/p&gt;

&lt;p&gt;En cas d'urgence ou de situation qui le réclame, votre porte doit&lt;br&gt;
évidemment rester ouverte. Mais une disponibilité permanente n'est pas un&lt;br&gt;
dispositif de suivi : c'est un modèle qui ne passe pas à l'échelle, et qui&lt;br&gt;
laisse de côté ceux qui ne viennent jamais frapper.&lt;/p&gt;

&lt;p&gt;La réponse, c'est d'instaurer un rendez-vous régulier avec chaque personne&lt;br&gt;
de l'équipe : un temps dédié, qui vous revient d'initier, pour échanger,&lt;br&gt;
prendre la température et vous donner mutuellement du feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn1lokutbi21vyn75nhot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn1lokutbi21vyn75nhot.png" alt="À gauche, des sollicitations dispersées tout au long de la semaine ; à droite, les mêmes échanges regroupés dans quelques créneaux réguliers" width="800" height="492"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;La porte ouverte laisse les sollicitations arriver n'importe quand ; le 1:1 régulier les rassemble dans un temps prévu pour ça.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Une régularité par individu
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Tu veux qu'on se voie à quelle régularité ? »&lt;br&gt;
(&lt;a href="https://www.linkedin.com/in/florentdubost/" rel="noopener noreferrer"&gt;Florent Dubost&lt;/a&gt;,&lt;br&gt;
mon manager pendant plusieurs années)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tout est affaire d'individu. Chacun a besoin de voir son manager à une&lt;br&gt;
fréquence et pour une durée qui lui sont propres. Décréter « je vois&lt;br&gt;
tout le monde trente minutes toutes les deux semaines », c'est la garantie&lt;br&gt;
de ne répondre au besoin de personne : certains étoufferont, d'autres&lt;br&gt;
tourneront en rond faute de matière.&lt;/p&gt;

&lt;p&gt;Cette cadence se négocie donc avec chaque managé. Elle n'a rien de figé et&lt;br&gt;
peut évoluer au fil du temps, mais une fois posée, elle devient un&lt;br&gt;
engagement : &lt;strong&gt;on ne l'annule pas, on la décale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Concrètement, je recommande de fixer explicitement, en échangeant avec la&lt;br&gt;
personne, la fréquence et la durée du 1:1, puis de les consigner quelque&lt;br&gt;
part. Un accord tacite se perd ; un accord écrit se relit et se révise&lt;br&gt;
quand le besoin change.&lt;/p&gt;
&lt;h2&gt;
  
  
  Le cas des alternants, stagiaires et profils juniors
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Je ne sais pas si je suis suffisamment à l'aise pour prendre un&lt;br&gt;
alternant… » (un collègue, en pleine crise de confiance)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le suivi des profils en apprentissage fait souvent peur : celle de mal s'en&lt;br&gt;
occuper, ou celle d'être sollicité en permanence. J'ai eu la chance de&lt;br&gt;
donner des cours, et l'expérience d'une classe de quarante étudiants qui&lt;br&gt;
vous demandent chacun à leur tour « il faut faire quoi, monsieur ? » forge&lt;br&gt;
assez vite l'envie de se doter d'une stratégie.&lt;/p&gt;

&lt;p&gt;Comme pour n'importe quel managé, la réponse passe par un suivi régulier,&lt;br&gt;
mais nettement plus soutenu. Ce que j'ai appliqué le plus souvent : deux&lt;br&gt;
créneaux d'une heure par semaine, bloqués dans le calendrier, généralement&lt;br&gt;
un le mardi et un le jeudi, pendant lesquels je leur consacre du temps pour&lt;br&gt;
avancer ensemble sur leur sujet. Ces points réguliers créent des&lt;br&gt;
respirations dans la semaine : la personne sait qu'un moment est prévu pour&lt;br&gt;
poser ses questions et ses blocages, ce qui évite les sollicitations&lt;br&gt;
permanentes. C'est aussi ce qui rend possible l'accompagnement de plusieurs&lt;br&gt;
apprenants en parallèle : votre capacité, c'est le temps que vous êtes prêt&lt;br&gt;
à bloquer chaque semaine.&lt;/p&gt;

&lt;p&gt;Un autre réflexe utile : confier deux sujets à la fois. Le premier, « l'os »,&lt;br&gt;
est le sujet difficile, celui sur lequel la personne peut rester coincée.&lt;br&gt;
Le second est un sujet sans blocage connu, sur lequel elle peut continuer à&lt;br&gt;
avancer librement quand l'os résiste. Elle n'est jamais complètement à&lt;br&gt;
l'arrêt, et vous n'êtes pas le seul goulot d'étranglement de sa semaine.&lt;/p&gt;

&lt;p&gt;L'arrivée d'un alternant ou d'un stagiaire, ça se prépare. J'y reviendrai&lt;br&gt;
plus en détail dans un article dédié à l'onboarding, l'offboarding et le&lt;br&gt;
recrutement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0k6u0r8jo4nh32qsfhzf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0k6u0r8jo4nh32qsfhzf.webp" alt="Jules Poissonnet et moi sur scène, micro en main, lors d'un talk" width="320" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Avec Jules Poissonnet, mon ancien alternant, sur la scène de LyonJS 100&lt;br&gt;
pour un talk sur les stratégies de test. Accompagner un profil junior, c'est&lt;br&gt;
aussi l'amener jusqu'à partager devant une salle.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Étaler ses 1:1 dans la semaine
&lt;/h2&gt;

&lt;p&gt;Qu'il s'agisse des 1:1 de vos managés ou de vos apprenants, placez-les dans&lt;br&gt;
un agenda partagé. Pourquoi pas un agenda dédié, avec sa propre couleur,&lt;br&gt;
pour visualiser d'un coup d'œil comment vos entretiens se répartissent. Le&lt;br&gt;
but est d'éviter la journée entièrement remplie de 1:1. Certains préfèrent&lt;br&gt;
tout enchaîner ; pour moi, ce serait un vrai cauchemar.&lt;/p&gt;

&lt;p&gt;Un 1:1 demande une écoute pleine et entière. En enchaîner cinq dans la même&lt;br&gt;
journée, c'est arriver au dernier vidé de son attention, à écouter à moitié&lt;br&gt;
la personne qui n'y est pour rien si elle passe en fin de liste. Chaque&lt;br&gt;
entretien laisse aussi du travail derrière lui : une note à prendre, une&lt;br&gt;
relance à faire, un sujet difficile à digérer avant de pouvoir passer à&lt;br&gt;
autre chose. Il faut un peu d'air entre deux pour ça. Enfin, espacer les&lt;br&gt;
1:1 leur laisse la souplesse de déborder : si l'un a besoin d'un quart&lt;br&gt;
d'heure de plus, il ne fait pas s'effondrer les quatre suivants.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx1jmusxf0gb2hv6ykj9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx1jmusxf0gb2hv6ykj9a.png" alt="Semaine type d'un manager : les 1:1 sont répartis un par jour, du lundi au vendredi, entre les réunions d'équipe" width="800" height="523"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Une semaine type : un 1:1 par jour, à des horaires différents, plutôt qu'une demi-journée entière d'entretiens enchaînés.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Des besoins qui ne s'équilibrent pas
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Il est normal que certaines personnes de vos équipes nécessitent un&lt;br&gt;
accompagnement plus fort que d'autres. » (Sarah Drasner, dans le livre&lt;br&gt;
&lt;a href="https://blog.slashgear.dev/posts/engineering-manager-role-et-posture" rel="noopener noreferrer"&gt;cité dans l'article&lt;br&gt;
précédent&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ça peut être frustrant quand on débute dans le rôle, mais avec l'expérience,&lt;br&gt;
ça devient une évidence. Chaque personne de l'équipe est différente, et&lt;br&gt;
l'accompagnement que vous lui apportez n'est ni le même ni comparable d'un&lt;br&gt;
individu à l'autre. Oui, vous aurez peut-être quelqu'un qui occupera à lui&lt;br&gt;
seul la moitié de votre temps de management sur une semaine. Avec le recul,&lt;br&gt;
ça ne me choque plus.&lt;/p&gt;

&lt;p&gt;Et tout cela évolue. Une personne que vous suivez de loin à un moment donné&lt;br&gt;
sera peut-être bien plus demandeuse l'année suivante. Rien n'est&lt;br&gt;
nécessairement corrélé à l'expérience ou à l'autonomie du managé : un profil&lt;br&gt;
junior vous mobilisera souvent davantage, mais vous croiserez aussi des&lt;br&gt;
juniors qui gagnent très vite en autonomie et se jettent dans les ronces&lt;br&gt;
avec appétit pour apprendre. Dans ce cas, laissez-les faire.&lt;/p&gt;

&lt;p&gt;Ne soyez donc pas surpris que les besoins soient déséquilibrés dans&lt;br&gt;
l'équipe. &lt;strong&gt;Cherchez l'équité, pas l'égalité&lt;/strong&gt; : donnez plus à celles et&lt;br&gt;
ceux qui en ont le plus besoin. &lt;strong&gt;C'est à vous de vous adapter à l'équipe,&lt;br&gt;
pas l'inverse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fubj1w5e1o9lcdwjlcshu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fubj1w5e1o9lcdwjlcshu.png" alt="Part du temps de management consacré à chaque personne de l'équipe sur une semaine : les barres sont très inégales, de 50 % pour l'une à 5 % pour une autre" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Un exemple : sur une semaine, une personne peut mobiliser la moitié de votre temps de management, une autre à peine 5 %. C'est normal.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Le syndrome du sauveteur compulsif
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Des fois j'y arrive, souvent j'me trompe, donc j'recommence, et puis&lt;br&gt;
j'apprends. » (Orelsan — et pourquoi pas Dorothée tant qu'on y est ? je&lt;br&gt;
cite qui je veux, hein)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quand on reçoit la responsabilité de manager, on a envie de protéger son&lt;br&gt;
équipe, de lui éviter les erreurs dans lesquelles on est soi-même tombé.&lt;br&gt;
L'intention est louable, mais elle prive l'équipe de l'essentiel de son&lt;br&gt;
apprentissage. L'image que je me fais du rôle, c'est plutôt : « je dois&lt;br&gt;
laisser mon équipe échouer pour qu'elle progresse. » C'est frustrant.&lt;/p&gt;

&lt;p&gt;Certains managers se diront « si je le fais moi-même, j'en ai pour dix&lt;br&gt;
minutes ». En réalité, &lt;strong&gt;vous en avez pour dix minutes &lt;em&gt;plus&lt;/em&gt; vos années&lt;br&gt;
d'expérience&lt;/strong&gt;. La personne que vous managez, elle, n'a pas ces années :&lt;br&gt;
elle est en train de les construire. Laissez-la échouer.&lt;/p&gt;

&lt;p&gt;Vous allez me trouver vilain, mais il m'est arrivé de créer sciemment des&lt;br&gt;
situations où je « soulève légèrement le tapis » pour qu'une personne de&lt;br&gt;
l'équipe s'y prenne les pieds. Rassurez-vous, c'est une image : mon équipe&lt;br&gt;
n'est pas sponsorisée par les crèmes à l'arnica. Provoquer des situations&lt;br&gt;
qui font grandir l'équipe fait partie du rôle. C'est dans une certaine zone&lt;br&gt;
d'inconfort que vos managés apprennent et progressent, pas en refaisant ce&lt;br&gt;
qu'ils maîtrisent déjà.&lt;/p&gt;

&lt;p&gt;Votre alarme interne doit évidemment continuer de sonner : il s'agit&lt;br&gt;
d'éviter que l'équipe échoue trop fort, ou se fasse vraiment mal. Pour&lt;br&gt;
distinguer l'incident formateur du vrai problème, je me sers d'un test&lt;br&gt;
simple : si je m'imagine y repenser dans deux ans et que je le trouve&lt;br&gt;
encore grave à ce moment-là, alors il est grave aujourd'hui et je dois&lt;br&gt;
intervenir. Sinon, je laisse faire.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tenir un journal de bord
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Journal de bord : mer calme, embarquement de 4 passagers, vent NE&lt;br&gt;
force 3… » (pas la citation d'un mentor cette fois, juste l'image qui m'a&lt;br&gt;
servi de modèle)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L'image en tête de cet article est une double page du livre de bord de la&lt;br&gt;
frégate néerlandaise &lt;em&gt;Medea&lt;/em&gt;, tenu en 1786 par son commandant A.A. Buyskes&lt;br&gt;
(&lt;a href="https://www.nationaalarchief.nl/" rel="noopener noreferrer"&gt;Nationaal Archief&lt;/a&gt;, domaine public). On y&lt;br&gt;
voit des profils de côtes dessinés à main levée et le relevé quotidien de la&lt;br&gt;
navigation : cap, vent, mouillages, incidents. Des siècles avant nos outils&lt;br&gt;
de suivi, la pratique était déjà là — noter chaque jour, factuellement, ce&lt;br&gt;
qu'on observe, pour pouvoir s'y reporter plus tard. C'est exactement l'usage&lt;br&gt;
que je vous propose d'en faire avec votre équipe.&lt;/p&gt;

&lt;p&gt;Avant même de parler du document qui me sert à structurer le suivi de mes&lt;br&gt;
managés, il y a un préalable : disposer quelque part d'un espace de notes.&lt;br&gt;
Vous observez votre équipe en continu, dans ses réussites comme dans ses&lt;br&gt;
ratés, à l'échelle collective comme individuelle. Avec une petite équipe, on&lt;br&gt;
a vite envie de s'en remettre à sa mémoire ; elle finit toujours par être&lt;br&gt;
débordée.&lt;/p&gt;

&lt;p&gt;Je conseille donc de tenir un journal de bord : un endroit où consigner les&lt;br&gt;
événements de la vie de l'équipe. Il vous est strictement personnel, et sa&lt;br&gt;
seule contrainte est de &lt;strong&gt;vous obliger à rester factuel&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On peut y noter la mise en production d'une fonctionnalité, un incident, la&lt;br&gt;
réussite marquante d'une personne, une communication qui est mal passée, un&lt;br&gt;
oubli de standup, ou encore une période où quelqu'un s'est visiblement&lt;br&gt;
beaucoup investi. Relus quelques semaines plus tard, au moment d'un 1:1 ou&lt;br&gt;
d'un point de carrière, ces faits datés valent bien mieux qu'une impression&lt;br&gt;
générale.&lt;/p&gt;

&lt;p&gt;Je vous montrerais bien le mien, mais comme je l'ai dit, il est privé. À&lt;br&gt;
vous de faire le vôtre.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fopwe6ss9uwlbshkwvdvk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fopwe6ss9uwlbshkwvdvk.png" alt="Page d'un journal de bord de manager avec des entrées datées et factuelles : mise en production, incident, investissement d'une personne, communication maladroite, oubli récurrent" width="800" height="554"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Des entrées courtes, datées et factuelles : ni interprétation ni jugement, juste ce qui s'est passé.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Le brag document : se souvenir de ses réussites
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Si on me demande quels sont mes succès de l'année, c'est très dur d'être&lt;br&gt;
exhaustif. » (Anne-Laure de Boissieu, une ancienne collègue et amie)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dans une &lt;a href="https://www.youtube.com/watch?v=vktamvlVBx8" rel="noopener noreferrer"&gt;conférence que je recommande&lt;br&gt;
vivement&lt;/a&gt;, mon ancienne&lt;br&gt;
collègue et amie Anne-Laure présente un concept que je ne connaissais pas et&lt;br&gt;
qui m'a beaucoup servi depuis : le &lt;em&gt;brag document&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;C'est un document proche du journal de bord, à ceci près qu'il ne recense&lt;br&gt;
que les fiertés, les sujets que l'on considère personnellement comme des&lt;br&gt;
succès. Notre cerveau fonctionne ainsi : on oublie très vite ce dont on&lt;br&gt;
devrait être fier, pendant que le problème sur lequel on bloque depuis deux&lt;br&gt;
semaines occupe presque tout l'espace.&lt;/p&gt;

&lt;p&gt;D'où l'intérêt de tenir un brag document avec chaque managé, et un autre&lt;br&gt;
pour l'équipe dans son ensemble. Chaque personne y historise avec vous, au&lt;br&gt;
fil de l'année, les fiertés qu'elle a eues : un titre, une date, une courte&lt;br&gt;
description. À vous de faire le même exercice à l'échelle de l'équipe.&lt;/p&gt;

&lt;p&gt;À quoi bon, me direz-vous ? Ce document se révèle précieux dans plusieurs&lt;br&gt;
situations :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accompagner un membre de l'équipe en questionnement, qui n'a pas
l'impression de progresser ou qui traverse un syndrome de l'imposteur ;&lt;/li&gt;
&lt;li&gt;préparer les périodes d'évaluation annuelle en s'appuyant sur tous les
succès de l'année, et pas seulement sur ceux des deux derniers mois ;&lt;/li&gt;
&lt;li&gt;défendre l'impact et la vision de votre équipe dans l'entreprise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Je ne rends pas justice ici à la richesse du talk d'Anne-Laure. Il vaut&lt;br&gt;
vraiment le détour :&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/vktamvlVBx8" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Donner du feedback
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Praise in public, feedback in private. » (Sarah Drasner)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Un manager passe une part énorme de son temps à donner du feedback. Je ne&lt;br&gt;
vais pas détailler ici &lt;em&gt;comment&lt;/em&gt; le formuler : le sujet mérite un article à&lt;br&gt;
lui seul. Je m'en tiens à quelques clés que l'expérience, et la lecture du&lt;br&gt;
livre de Sarah, m'ont fait comprendre.&lt;/p&gt;

&lt;p&gt;Commençons par le feedback positif. Quand quelqu'un de votre équipe fait&lt;br&gt;
quelque chose que vous trouvez franchement bien, vous &lt;em&gt;devez&lt;/em&gt; le dire,&lt;br&gt;
positivement, devant toute l'équipe. Et le redire lors du 1:1 suivant.&lt;/p&gt;

&lt;p&gt;Pourquoi deux fois ? Parce que chacun des deux canaux, seul, laisse un&lt;br&gt;
doute. En privé uniquement : « il me dit ça pour me faire plaisir, il ne le&lt;br&gt;
pense pas vraiment ». En public uniquement : « il le dit devant tout le&lt;br&gt;
monde, mais je ne sais pas ce qu'il en pense réellement ». Les deux&lt;br&gt;
ensemble lèvent l'ambiguïté.&lt;/p&gt;

&lt;p&gt;Tout cela suppose d'avoir noté ce qui s'est passé — c'est l'un des usages de&lt;br&gt;
mon journal de bord. Des faits consignés factuellement, ce sont des&lt;br&gt;
occasions de feedback prêtes à l'emploi. Et vous verrez qu'une personne&lt;br&gt;
ajoute bien plus facilement une ligne à son brag document quand vous avez&lt;br&gt;
commencé par reconnaître la chose vous-même.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;C'est quand, la dernière fois que vous avez dit à quelqu'un de votre équipe&lt;br&gt;
qu'il ou elle avait fait du bon travail ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Vient ensuite le feedback plus difficile : les rectifications, les axes&lt;br&gt;
d'amélioration.&lt;/p&gt;

&lt;p&gt;Sur ce terrain, l'expérience m'a appris qu'il existe très peu de situations&lt;br&gt;
qui justifient un feedback direct, sur le moment. La principale exception :&lt;br&gt;
le danger immédiat. Si quelqu'un dit ou fait quelque chose qui peut nuire&lt;br&gt;
directement à une autre personne, là, j'interviens tout de suite.&lt;/p&gt;

&lt;p&gt;En dehors de ça, laisser le feedback mijoter est plutôt une bonne chose.&lt;br&gt;
Qu'est-ce qui vous empêche de noter les faits et d'en reparler à froid lors&lt;br&gt;
d'un prochain 1:1 ? Ce délai vous laisse réfléchir à la manière d'amener les&lt;br&gt;
choses, et, la plupart du temps, il fait retomber la pression : « en fait,&lt;br&gt;
ce n'est pas si grave ». &lt;strong&gt;Si un feedback ne vaut pas la peine d'attendre&lt;br&gt;
une à deux semaines pour être donné, est-ce vraiment un feedback&lt;br&gt;
pertinent ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;À l'inverse du feedback positif, celui-ci se donne uniquement en privé, dans&lt;br&gt;
un échange en tête-à-tête avec la personne.&lt;/p&gt;
&lt;h2&gt;
  
  
  Les valeurs et les préférences
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Tu verras que la source première des conflits dans une équipe, ce sont&lt;br&gt;
des conflits de valeurs. Et l'ego, bien sûr. »&lt;br&gt;
(&lt;a href="https://www.linkedin.com/in/kenny-d-3761b59b/" rel="noopener noreferrer"&gt;Kenny Dits&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si les frictions naissent surtout d'un désaccord sur ce qui compte, alors&lt;br&gt;
une partie du travail de suivi consiste à savoir ce qui compte pour chacun,&lt;br&gt;
avant que ça ne coince.&lt;/p&gt;

&lt;p&gt;Sarah Drasner propose pour cela un exercice tout simple dans son livre :&lt;br&gt;
poser directement la question à vos managés, en 1:1. Trois questions&lt;br&gt;
suffisent :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dans ton travail, quelles sont les trois choses les plus importantes pour
toi ?&lt;/li&gt;
&lt;li&gt;Qu'est-ce que tu préfères faire ?&lt;/li&gt;
&lt;li&gt;Qu'est-ce que tu aimes le moins faire ?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rien qu'en posant ces questions, vous découvrirez que tout le monde ne&lt;br&gt;
classe pas les choses dans le même ordre que vous. L'un vous parlera des&lt;br&gt;
collègues et de l'ambiance, l'autre de la qualité du code, un troisième de&lt;br&gt;
l'impact pour les utilisateurs, un quatrième de cohérence. C'est très varié.&lt;br&gt;
Cet exercice vous fait mieux connaître les personnes de votre équipe et vous&lt;br&gt;
évite de leur plaquer un archétype construit sur vous-même ou sur un autre&lt;br&gt;
collègue.&lt;/p&gt;

&lt;p&gt;Vous remarquerez aussi que certaines personnes n'évoquent pas du tout&lt;br&gt;
certaines thématiques dans leur top 3. Ça ne veut pas dire que ça ne compte&lt;br&gt;
pas pour elles, seulement que ce n'est pas leur priorité. Et &lt;strong&gt;ces&lt;br&gt;
non-équivalences de priorité sont souvent à la racine des problèmes&lt;br&gt;
d'équipe&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imaginez Alex, « j'aime quand on règle vite les soucis des utilisateurs sans&lt;br&gt;
trop débattre », et Sam, « j'aime quand on prend le temps de réfléchir au&lt;br&gt;
problème pour trouver une solution qui répond parfaitement au besoin ».&lt;br&gt;
Inutile de faire un dessin : Alex et Sam finiront par se retrouver en&lt;br&gt;
tension. Mais si vous le savez, et que vous savez maintenant comment chacun&lt;br&gt;
fonctionne, vous pouvez mettre un peu d'eau dans leurs vins respectifs et&lt;br&gt;
trouver des compromis qui ménagent les valeurs des deux.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fintpvs3i009n68siq925.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fintpvs3i009n68siq925.png" alt="Le top 3 de deux personnes de la même équipe : les priorités et leur ordre diffèrent, et une préoccupation commune n'a pas le même rang chez l'une et chez l'autre" width="800" height="397"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Le même exercice, deux classements très différents — et une préoccupation commune qui ne pèse pas du tout le même poids d'une personne à l'autre.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cet exercice se fait en 1:1. Mais quand l'équipe est suffisamment à l'aise,&lt;br&gt;
il devient aussi un bon jeu collectif : chacun arrive avec ses trois post-it&lt;br&gt;
par question, on mélange le tout, et l'équipe doit retrouver quelle réponse&lt;br&gt;
va à qui, et dans quel ordre.&lt;/p&gt;

&lt;p&gt;Connaître les préférences, ensuite, ce n'est pas pour ne distribuer que des&lt;br&gt;
tâches que chacun adore et cloisonner l'équipe. C'est pour savoir ce que les&lt;br&gt;
gens aiment et ce qu'ils détestent. On a tous une jauge intérieure : elle se&lt;br&gt;
vide quand on fait des choses qui nous pèsent, elle se recharge quand on&lt;br&gt;
fait des choses qui nous plaisent. En connaissant les deux, vous pouvez&lt;br&gt;
aider chacun à tenir cet équilibre.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmr8u1uouymwr5tndcyz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmr8u1uouymwr5tndcyz4.png" alt="Une jauge intérieure : les tâches qui pèsent la vident, les tâches qui plaisent la rechargent ; le rôle du manager est d'aider à maintenir l'équilibre sur la durée" width="800" height="385"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Chacun porte cette jauge. Le travail de suivi, c'est de faire en sorte qu'elle ne tombe pas durablement à zéro.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Toutes ces réponses se rangent dans la page de suivi de la personne, et vous&lt;br&gt;
y reviendrez : pour répartir les sujets, pour comprendre une tension, pour&lt;br&gt;
vérifier de temps en temps que ce qui comptait il y a un an compte encore.&lt;/p&gt;
&lt;h2&gt;
  
  
  Suivre et encourager l'apprentissage
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;« Un jour j'ai monté un truc, les &lt;em&gt;Last Friday Talks&lt;/em&gt; : une demi-journée&lt;br&gt;
de conférences tech internes, le dernier vendredi du mois, pour que les&lt;br&gt;
gens racontent ce qu'ils font. Dès la deuxième édition ce n'était plus le&lt;br&gt;
dernier vendredi, mais peu importe. »&lt;br&gt;
(&lt;a href="https://www.linkedin.com/in/kenny-d-3761b59b/" rel="noopener noreferrer"&gt;Kenny Dits&lt;/a&gt; — encore lui)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le manager a le devoir d'accompagner l'apprentissage de son équipe, sur deux&lt;br&gt;
axes qui vont ensemble : s'assurer que chacun a le temps et les moyens&lt;br&gt;
d'apprendre, et s'assurer que personne ne se disperse.&lt;/p&gt;

&lt;p&gt;J'ai fini par en faire un petit rituel. On liste ensemble, dans un tableau,&lt;br&gt;
les sujets de veille technique que la personne veut explorer, avec pour&lt;br&gt;
chacun : le sujet, son état, la date de départ, une durée estimée, une&lt;br&gt;
courte description et un pourcentage d'avancement. Tout y passe : des&lt;br&gt;
conférences à voir en replay, des articles à lire, des &lt;em&gt;proof of concept&lt;/em&gt;&lt;br&gt;
d'outils ou de technos, des formations, des certifications.&lt;/p&gt;

&lt;p&gt;Un extrait, colonne &lt;em&gt;description&lt;/em&gt; mise de côté pour la lisibilité :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sujet&lt;/th&gt;
&lt;th&gt;État&lt;/th&gt;
&lt;th&gt;Départ&lt;/th&gt;
&lt;th&gt;Durée est.&lt;/th&gt;
&lt;th&gt;Avancement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Talk « Local-first » dotJS&lt;/td&gt;
&lt;td&gt;partagé&lt;/td&gt;
&lt;td&gt;2026-02-10&lt;/td&gt;
&lt;td&gt;2 h&lt;/td&gt;
&lt;td&gt;100 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PoC OpenTelemetry&lt;/td&gt;
&lt;td&gt;terminé&lt;/td&gt;
&lt;td&gt;2026-01-15&lt;/td&gt;
&lt;td&gt;1 j&lt;/td&gt;
&lt;td&gt;100 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust pour l'embarqué&lt;/td&gt;
&lt;td&gt;en cours&lt;/td&gt;
&lt;td&gt;2026-03-02&lt;/td&gt;
&lt;td&gt;3 j&lt;/td&gt;
&lt;td&gt;60 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certification CKA&lt;/td&gt;
&lt;td&gt;demandé&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;5 j&lt;/td&gt;
&lt;td&gt;0 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Une seule règle : on ne démarre un sujet que s'il n'y en a pas déjà deux en&lt;br&gt;
cours. Autrement dit, pour en commencer un nouveau, il faut d'abord en&lt;br&gt;
terminer un, ou l'abandonner assumé.&lt;/p&gt;

&lt;p&gt;Les états possibles : &lt;em&gt;demandé&lt;/em&gt;, &lt;em&gt;rejeté&lt;/em&gt; (très rare — sauf si quelqu'un&lt;br&gt;
vous annonce qu'il aimerait apprendre le saut en parachute), &lt;em&gt;en cours&lt;/em&gt;,&lt;br&gt;
&lt;em&gt;terminé&lt;/em&gt;, et &lt;em&gt;partagé&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ce dernier état est le plus important. Pour moi, &lt;strong&gt;un apprentissage n'est&lt;br&gt;
vraiment terminé que lorsqu'il a été partagé&lt;/strong&gt; : à l'équipe, à la guilde, à&lt;br&gt;
la communauté. C'est là que la citation de Kenny prend tout son sens — un&lt;br&gt;
espace de partage régulier, même bancal, même plus vraiment le dernier&lt;br&gt;
vendredi du mois, donne une destination à tout ce travail de veille.&lt;/p&gt;

&lt;p&gt;Je dois avouer qu'une fois le principe bien ancré et adopté par l'équipe, le&lt;br&gt;
suivi dans le tableau n'est plus vraiment nécessaire. Mais il aide à poser&lt;br&gt;
les bases.&lt;/p&gt;
&lt;h2&gt;
  
  
  Le template de page de suivi individuel
&lt;/h2&gt;

&lt;p&gt;Tout ce qui précède se range quelque part. Ce que j'ai fini par construire,&lt;br&gt;
c'est une page unique par managé — un &lt;em&gt;hub&lt;/em&gt; de suivi. Chez moi elle vit dans&lt;br&gt;
Confluence, mais Notion, un Google Doc ou n'importe quel autre outil font&lt;br&gt;
tout aussi bien l'affaire. Le point important : elle n'est accessible qu'à&lt;br&gt;
deux personnes, la personne concernée et moi.&lt;/p&gt;

&lt;p&gt;Elle rassemble, au même endroit :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;l'identité et la date d'arrivée ;&lt;/li&gt;
&lt;li&gt;les &lt;strong&gt;valeurs et préférences&lt;/strong&gt; : le top 3 de ce qui compte, les sujets
préférés, les sujets moins aimés ;&lt;/li&gt;
&lt;li&gt;le &lt;strong&gt;brag document&lt;/strong&gt; : titre, date, description ;&lt;/li&gt;
&lt;li&gt;l'&lt;strong&gt;apprentissage supervisé&lt;/strong&gt; : le tableau vu plus haut.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voici un squelette que vous pouvez copier tel quel :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Suivi — Prénom NOM&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Date d'arrivée :**&lt;/span&gt; AAAA-MM-JJ

&lt;span class="gu"&gt;## Valeurs et préférences&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Ce qui compte le plus (top 3) :**&lt;/span&gt;
&lt;span class="p"&gt;  1.&lt;/span&gt; …
&lt;span class="p"&gt;  2.&lt;/span&gt; …
&lt;span class="p"&gt;  3.&lt;/span&gt; …
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Sujets préférés :**&lt;/span&gt; …
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Sujets moins aimés :**&lt;/span&gt; …

&lt;span class="gu"&gt;## Brag document&lt;/span&gt;

| Titre | Date       | Description |
| ----- | ---------- | ----------- |
| …     | AAAA-MM-JJ | …           |

&lt;span class="gu"&gt;## Apprentissage supervisé&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- États : demandé · rejeté · en cours · terminé · partagé --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Règle : jamais plus de 2 sujets « en cours » en même temps --&amp;gt;&lt;/span&gt;

| Sujet | État | Départ     | Durée est. | Description | Avancement |
| ----- | ---- | ---------- | ---------- | ----------- | ---------- |
| …     | …    | AAAA-MM-JJ | …          | …           | … %        |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ce hub, c'est en quelque sorte le résumé matériel de cet article. Et les&lt;br&gt;
notes de chaque 1:1 deviennent des pages enfant de cette page : le suivi&lt;br&gt;
dans la durée d'un côté, le fil des échanges de l'autre.&lt;/p&gt;

&lt;p&gt;Ce que je ne détaille pas ici, c'est &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-triangle-et-suivi-des-irritants" rel="noopener noreferrer"&gt;ce qu'on se dit réellement en 1:1 et ce&lt;br&gt;
qu'on cherche à y adresser&lt;/a&gt;&lt;br&gt;
: c'est un sujet à part entière.&lt;/p&gt;

&lt;h2&gt;
  
  
  Et la suite ?
&lt;/h2&gt;

&lt;p&gt;Il reste d'autres aspects du 1:1 et du suivi que je n'ai pas abordés ici. Si&lt;br&gt;
vous voulez que je précise un point ou que j'aborde un thème en particulier,&lt;br&gt;
mes réseaux sont accessibles depuis ce blog : écrivez-moi, je serai ravi de&lt;br&gt;
vous lire.&lt;/p&gt;

&lt;p&gt;Ce thème des citations et principes reçus se poursuivra dans un prochain&lt;br&gt;
article consacré à &lt;strong&gt;« Donner des objectifs et de la vision »&lt;/strong&gt;. Comme&lt;br&gt;
toujours, vous pouvez retrouver l'ensemble de la série via le tag&lt;br&gt;
&lt;a href="https://blog.slashgear.dev/tags/engineering-management" rel="noopener noreferrer"&gt;engineering-management&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
      <category>french</category>
    </item>
    <item>
      <title>Engineering manager : rôle et posture</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Thu, 15 Oct 2026 17:02:49 +0000</pubDate>
      <link>https://dev.to/slashgear_/engineering-manager-role-et-posture-m4p</link>
      <guid>https://dev.to/slashgear_/engineering-manager-role-et-posture-m4p</guid>
      <description>&lt;p&gt;&lt;em&gt;Cet article a été publié initialement sur &lt;a href="https://blog.slashgear.dev/posts/engineering-manager-role-et-posture/" rel="noopener noreferrer"&gt;mon blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Avant de commencer, une précision : ce qui suit n'est ni une méthode ni une&lt;br&gt;
vérité générale. C'est un retour d'expérience personnel, forcément subjectif,&lt;br&gt;
sur ce que le rôle d'engineering manager m'a appris. Je n'ai aucune prétention&lt;br&gt;
à la démonstration, juste l'envie de partager honnêtement ce qui m'a construit&lt;br&gt;
dans ce rôle.&lt;/p&gt;

&lt;p&gt;C'est le premier article d'une série où je vais explorer, à chaque fois avec&lt;br&gt;
un focus différent, ce que ce métier m'a apporté. Plutôt que de vous exposer&lt;br&gt;
mes propres principes, j'ai préféré faire autrement : partager les citations&lt;br&gt;
et les principes que des personnes qui ont compté dans mon parcours&lt;br&gt;
(collègues, mentors) m'ont transmis, et vous dire ce qu'ils m'apportent au&lt;br&gt;
quotidien. Ce premier épisode porte sur le rôle et la posture d'engineering&lt;br&gt;
manager.&lt;/p&gt;

&lt;p&gt;Si ces articles ouvrent des voies à quelques personnes, alors j'en serai&lt;br&gt;
déjà ravi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Une référence avant les citations : "Engineering Management for the Rest of Us"
&lt;/h2&gt;

&lt;p&gt;Avant d'entrer dans les citations qui ont individuellement marqué mon&lt;br&gt;
parcours, je ne peux pas commencer cette série sans mentionner une&lt;br&gt;
référence : le livre &lt;a href="https://www.engmanagement.dev/" rel="noopener noreferrer"&gt;&lt;em&gt;Engineering Management for the Rest of&lt;br&gt;
Us&lt;/em&gt;&lt;/a&gt; de Sarah Drasner.&lt;/p&gt;

&lt;p&gt;C'est une vraie recommandation, du contenu de grande qualité. En un format&lt;br&gt;
relativement court, elle aborde énormément de sujets et formalise une&lt;br&gt;
multitude de concepts et de conseils de façon très simple. Si vous ne devez&lt;br&gt;
lire qu'un seul livre sur le sujet, c'est celui-là.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faw0mc49rii43k1zt4m21.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faw0mc49rii43k1zt4m21.webp" alt="Dédicace de Sarah Drasner dans mon exemplaire du livre" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Merci Sarah pour cette dédicace, obtenue lors de la DotJS 2025 où nous étions&lt;br&gt;
tous les deux speakers et avons pu échanger brièvement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Je citerai quand même un conseil tiré du livre : demander explicitement les&lt;br&gt;
attentes qu'ont vos managés de vous.&lt;/p&gt;

&lt;p&gt;C'est un exercice très sain, et vous verrez que chaque individu a des&lt;br&gt;
besoins et des attentes très différentes. C'est là aussi que ressort une&lt;br&gt;
nécessité importante : il vous faudra vous adapter à chaque individu de&lt;br&gt;
l'équipe. Une méthode unique ne fonctionnera pas avec tout le monde, sinon&lt;br&gt;
ce serait de la magie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le rôle du manager vs. celui de l'IC : hands on, hands off
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Tout ce que tu fais, l'équipe doit savoir et pouvoir le faire, mis à part&lt;br&gt;
les aspects de management." (&lt;a href="https://www.linkedin.com/in/florentdubost/" rel="noopener noreferrer"&gt;Florent Dubost&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tous mes modèles en management d'équipe d'ingénierie ont toujours gardé un&lt;br&gt;
aspect technique dans leur travail. Le pourcentage varie d'une personne à&lt;br&gt;
l'autre, mais iels ont toujours eu les mains dedans.&lt;/p&gt;

&lt;p&gt;Pour moi, engineering manager est un rôle étendu du rôle de contributeur&lt;br&gt;
individuel — l'IC&lt;sup&gt;1&lt;/sup&gt; —, pas un rôle qui s'y substitue.&lt;/p&gt;

&lt;p&gt;C'est aussi ce que j'en interprète : se réserver des tâches et des sujets en&lt;br&gt;
tant que "chef" est une mauvaise pratique. Si on veut pouvoir compter sur son&lt;br&gt;
équipe, toute l'équipe doit pouvoir nous épauler.&lt;/p&gt;

&lt;p&gt;Comment fonctionnerait l'équipe en cas de vacances si vous silotez des étapes&lt;br&gt;
de développement, de planification, ou des tâches spécifiques ?&lt;/p&gt;

&lt;h2&gt;
  
  
  La posture face à l'équipe : confiance et délégation
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Ta priorité c'est ton équipe, puis les individus qui composent ton&lt;br&gt;
équipe, et enfin les projets que ton équipe doit réaliser."&lt;br&gt;
(&lt;a href="https://www.linkedin.com/in/kenny-d-3761b59b/" rel="noopener noreferrer"&gt;Kenny Dits&lt;/a&gt;, mon chef&lt;br&gt;
pendant de nombreuses années chez M6)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;J'ai eu la chance d'avoir des managers humanistes. Je pense qu'il est&lt;br&gt;
nécessaire de prioriser l'équipe pour construire de la confiance, et qu'avec&lt;br&gt;
cette confiance, il n'y a plus de crainte à avoir pour l'accomplissement des&lt;br&gt;
objectifs.&lt;/p&gt;

&lt;p&gt;Pour la priorisation, il faut comprendre quelque chose qui n'apparaît pas&lt;br&gt;
forcément à la première lecture. Cela signifie que vous devrez parfois&lt;br&gt;
abandonner des projets ou des objectifs pour protéger les individus ou votre&lt;br&gt;
équipe. Cela signifie également qu'il sera parfois nécessaire de sortir&lt;br&gt;
quelqu'un de l'équipe, pour le bien de l'équipe. Ce sera votre&lt;br&gt;
responsabilité, autant s'y préparer. On ne sacrifie pas une équipe pour un&lt;br&gt;
individu ou un projet.&lt;/p&gt;

&lt;p&gt;De plus, votre équipe saura répondre présente lors d'un besoin plus&lt;br&gt;
important, car elle sait que si vous la sollicitez, c'est vraiment pour un&lt;br&gt;
sujet qui compte.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj1h8dkjbxb82cihuwqwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj1h8dkjbxb82cihuwqwk.png" alt=" " width="800" height="560"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;La priorité descend de la base vers le sommet : d'abord l'équipe, puis les individus qui la composent, enfin les projets.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Oui, c'est une référence troll à la pyramide des tests, un modèle que tout le&lt;br&gt;
monde connaît, que tout le monde cite, et que tout le monde finit par&lt;br&gt;
retoucher à sa sauce tant il ne veut plus dire grand-chose. J'en ai&lt;br&gt;
justement parlé dans une conférence avec Jules Poissonnet, mon ancien&lt;br&gt;
alternant : &lt;a href="https://www.youtube.com/watch?v=I_zNxGqRI3w" rel="noopener noreferrer"&gt;&lt;em&gt;Tester c'est tricher !&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  La posture de challenge : débloquer et faire grandir
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Qu'est-ce qui t'en empêche ?" (&lt;a href="https://verry.org/" rel="noopener noreferrer"&gt;Yann Verry&lt;/a&gt;, mon&lt;br&gt;
ancien collègue, qui me l'a dit plusieurs fois)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Un manager, c'est aussi quelqu'un qui challenge et qui permet à son équipe&lt;br&gt;
de sortir de ses blocages. C'est aussi être là pour débloquer des&lt;br&gt;
situations, mentorer, accompagner dans des décisions.&lt;/p&gt;

&lt;p&gt;Un manager est également là pour faire en sorte que les individus qui&lt;br&gt;
composent son équipe puissent s'épanouir et progresser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que "servir" son équipe veut dire concrètement
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Je fonctionne comme ça, je fais confiance de base et après si besoin je&lt;br&gt;
discute avec toi si il y a des choses à revoir."&lt;br&gt;
(&lt;a href="https://www.linkedin.com/in/%C3%A9tienne-de-thoury-a9290795/" rel="noopener noreferrer"&gt;Étienne de Thoury&lt;/a&gt;,&lt;br&gt;
mon actuel Head of chez Scaleway)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L'idée est simple : éviter le micromanagement, laisser de la liberté.&lt;/p&gt;

&lt;p&gt;Quand on devient manager, on peut parfois ressentir du stress et se&lt;br&gt;
retrouver à vouloir tout suivre, tout gérer.&lt;/p&gt;

&lt;p&gt;C'est aussi souvent nos propres managers qui nous poussent, sans le&lt;br&gt;
vouloir, vers ce travers : quand un n+1 demande un reporting ultra précis,&lt;br&gt;
il ne reste pas beaucoup d'autre choix que de commencer à tout suivre. Je le&lt;br&gt;
vois autrement : l'information en elle-même n'est pas un pouvoir. Savoir&lt;br&gt;
vers qui la renvoyer l'est bien plus, car elle sera toujours plus pertinente&lt;br&gt;
venant de la personne qui la maîtrise réellement.&lt;/p&gt;

&lt;p&gt;J'en ai une autre lecture, qui me parle tout autant : ce stress est souvent&lt;br&gt;
moins une question de contrôle que de confiance en soi. Si je vous confie&lt;br&gt;
une tâche alors que moi-même je ne suis pas certain à 100 % du résultat, que&lt;br&gt;
je n'ai pas confiance en moi pour la mener à bien, il me sera impossible de&lt;br&gt;
vous faire confiance pour la réaliser. Et cette incertitude finit par&lt;br&gt;
retomber sur le dos de la personne, ou de l'équipe.&lt;/p&gt;

&lt;h2&gt;
  
  
  L'effet miroir
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Quand tu réagis ou tu parles mal d'un projet ou d'une équipe en tant que&lt;br&gt;
manager, tu peux être sûr que ton équipe réagira en ×10 dans quelques&lt;br&gt;
semaines." (&lt;a href="https://www.linkedin.com/in/florent-dubost-914346181/" rel="noopener noreferrer"&gt;Florent Dubost&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Qu'on le veuille ou non, un manager est un modèle. Son équipe observe ses&lt;br&gt;
réactions, son ton, la façon dont il parle d'un projet, d'une autre équipe,&lt;br&gt;
d'une décision. Ce qui est capté, consciemment ou non, c'est ça qui se&lt;br&gt;
répète et s'amplifie dans l'équipe au fil des semaines.&lt;/p&gt;

&lt;p&gt;Si je me montre négatif ou désabusé sur un sujet, cette attitude ne reste pas&lt;br&gt;
la mienne. Elle infuse les conversations de l'équipe, puis sa manière&lt;br&gt;
d'aborder ce sujet avec les autres, et elle finit par me revenir, amplifiée.&lt;br&gt;
L'inverse est vrai aussi : la confiance et l'énergie qu'on montre se&lt;br&gt;
propagent de la même façon.&lt;/p&gt;

&lt;p&gt;Ça veut dire faire attention non seulement à ce que je dis, mais à comment&lt;br&gt;
je le dis, car c'est ça qui devient la norme de l'équipe, bien plus que&lt;br&gt;
n'importe quelle consigne explicite.&lt;/p&gt;

&lt;h2&gt;
  
  
  La sécurité psychologique : une responsabilité de leader
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Chaque leader de chaque groupe d'influence est responsable de la sécurité&lt;br&gt;
psychologique de son groupe d'influence." (Léa Coston, dans son talk&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=a670TeroQS0" rel="noopener noreferrer"&gt;&lt;em&gt;Les gens ne savent pas ce qu'ils font, la plupart du&lt;br&gt;
temps !&lt;/em&gt;&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En tant qu'engineering manager, on est le leader du groupe d'influence que&lt;br&gt;
constitue son équipe. La sécurité psychologique n'est pas un supplément&lt;br&gt;
d'âme ou une option "sympa à avoir" : c'est une responsabilité structurelle&lt;br&gt;
du rôle.&lt;/p&gt;

&lt;p&gt;Ce n'est d'ailleurs pas qu'une conviction personnelle : le &lt;a href="https://rework.withgoogle.com/intl/en/guides/understand-team-effectiveness" rel="noopener noreferrer"&gt;projet&lt;br&gt;
Aristote&lt;/a&gt;&lt;br&gt;
mené par Google, qui a étudié des centaines d'équipes internes, a montré que&lt;br&gt;
le facteur le plus déterminant de la performance d'une équipe n'était ni les&lt;br&gt;
compétences individuelles ni l'ancienneté, mais bien son niveau de sécurité&lt;br&gt;
psychologique.&lt;/p&gt;

&lt;p&gt;Une équipe en sécurité psychologique, c'est une équipe qui ose dire qu'elle&lt;br&gt;
s'est trompée, qui ose remonter un problème avant qu'il ne devienne&lt;br&gt;
critique, qui ose ne pas être d'accord avec vous. Une équipe qui n'a pas&lt;br&gt;
cette sécurité apprend vite à se taire, et vous perdez l'information la plus&lt;br&gt;
précieuse : celle qui arrive tôt.&lt;/p&gt;

&lt;p&gt;Il y a là un vrai enjeu d'exemplarité : être capable de dire à son équipe&lt;br&gt;
qu'on peut se tromper, qu'on s'est trompé, ou qu'on ne sait pas, ça rassure&lt;br&gt;
chacun sur sa propre capacité à faire ou dire la même chose.&lt;/p&gt;

&lt;p&gt;Ça rejoint directement l'effet miroir vu plus haut : la sécurité&lt;br&gt;
psychologique ne se décrète pas, elle se construit par la façon dont vous&lt;br&gt;
réagissez, en particulier quand quelque chose se passe mal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le bâton de pouvoir
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Le bâton de pouvoir" (une phrase lue quelque part, dont la source&lt;br&gt;
m'échappe malheureusement)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ve7hw7u16mlmz9e0fbm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ve7hw7u16mlmz9e0fbm.webp" alt="Main de pouvoir en ivoire sertie de pierres précieuses, montée sur un bâton doré" width="225" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo : &lt;a href="https://collections.louvre.fr/ark:/53355/cl010110048" rel="noopener noreferrer"&gt;collection du musée du Louvre&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Lorsqu'on décide de vous nommer responsable d'une équipe, cela signifie&lt;br&gt;
qu'on a confiance en vous pour vous donner ce pouvoir de décision. Ce&lt;br&gt;
pouvoir est un acte de confiance important, mais c'est aussi un fardeau : il&lt;br&gt;
faut, dans la mesure du possible, éviter de trop l'utiliser au sein de votre&lt;br&gt;
équipe.&lt;/p&gt;

&lt;p&gt;Les décisions doivent rester collectives, mais il vous faudra parfois&lt;br&gt;
arbitrer, plusieurs fois. C'est ce qu'on attend de vous, et non d'escalader&lt;br&gt;
la décision plus haut si son scope ne concerne que votre équipe.&lt;/p&gt;

&lt;p&gt;Une décision collective embarquera toujours bien plus l'équipe qu'une&lt;br&gt;
décision descendante. Faire émerger le maximum d'intelligence collective&lt;br&gt;
est, à mes yeux, l'une des missions les plus importantes d'un manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Se créer un réseau de managers
&lt;/h2&gt;

&lt;p&gt;Un dernier point, essentiel selon moi : se créer un réseau de managers, dans&lt;br&gt;
son entreprise ou ailleurs, avec qui échanger sur ces sujets, prendre des&lt;br&gt;
avis, des conseils. Je vous assure que ça aide, énormément.&lt;/p&gt;

&lt;p&gt;Une pensée pour les managers avec qui j'échange régulièrement : ils se&lt;br&gt;
reconnaîtront, merci à eux.&lt;/p&gt;

&lt;p&gt;Si vous n'en avez pas encore, des communautés en ligne existent, comme le&lt;br&gt;
Slack &lt;a href="https://engmanagers.github.io/" rel="noopener noreferrer"&gt;Engineering Managers Community&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Et la suite ?
&lt;/h2&gt;

&lt;p&gt;C'est le premier article de cette série sur mon retour d'expérience en tant&lt;br&gt;
qu'engineering manager. Le prochain épisode abordera le &lt;strong&gt;1:1 et le suivi&lt;br&gt;
individuel&lt;/strong&gt;. Vous pouvez retrouver tous les articles de la série via le tag&lt;br&gt;
engineering-management.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;IC, pour &lt;em&gt;individual contributor&lt;/em&gt; (contributeur individuel) : un membre de l'équipe qui contribue directement à la production — code, design, etc. — sans responsabilité de management.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
      <category>french</category>
    </item>
    <item>
      <title>Managing Terraform Modules with Nx Monorepo</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Sun, 02 Nov 2025 20:04:37 +0000</pubDate>
      <link>https://dev.to/slashgear_/managing-terraform-modules-with-nx-monorepo-4jfa</link>
      <guid>https://dev.to/slashgear_/managing-terraform-modules-with-nx-monorepo-4jfa</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Managing infrastructure as code at scale is challenging. When your organization grows beyond a handful of Terraform modules, you quickly encounter familiar problems: code duplication, inconsistent testing practices, unclear dependencies between modules, and the eternal question of whether to use a monorepo or split everything into separate repositories.&lt;/p&gt;

&lt;p&gt;If you've worked with Terraform in a team environment, you've probably faced these pain points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running &lt;code&gt;terraform fmt&lt;/code&gt; and &lt;code&gt;terraform validate&lt;/code&gt; across dozens of modules manually&lt;/li&gt;
&lt;li&gt;Uncertainty about which modules are affected by a change&lt;/li&gt;
&lt;li&gt;Difficulty enforcing consistent standards across all modules&lt;/li&gt;
&lt;li&gt;Time wasted running tests on unaffected modules in CI/CD&lt;/li&gt;
&lt;li&gt;The challenge of managing dependencies between related modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What if you could leverage the same powerful tooling that frontend teams use to manage their monorepos? Enter &lt;strong&gt;Nx&lt;/strong&gt; - a smart build system that can transform how you manage Terraform modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to see this in action?&lt;/strong&gt; I've created a &lt;a href="https://github.com/Slashgear/nx-terraform-demo" rel="noopener noreferrer"&gt;complete demo repository&lt;/a&gt; showcasing all the concepts covered in this article, with seven Scaleway Terraform modules fully configured with Nx.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge of Managing Terraform Modules at Scale
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Multi-Repo Dilemma
&lt;/h3&gt;

&lt;p&gt;Many organizations start with separate Git repositories for each Terraform module. This approach seems clean at first - each module has its own versioning, CI/CD pipeline, and release cycle. However, as your infrastructure grows, this strategy reveals its weaknesses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Versioning Hell&lt;/strong&gt;: When Module A depends on Module B, you need to carefully manage version pinning. Update Module B? Now you need to update Module A's version constraint, test it, and release a new version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Module Changes&lt;/strong&gt;: A breaking change that affects multiple modules requires coordinating PRs across multiple repositories, each with its own review and merge timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconsistent Tooling&lt;/strong&gt;: Each repository might have different CI configurations, different testing approaches, or even different versions of tools like &lt;code&gt;tflint&lt;/code&gt; or &lt;code&gt;terraform-docs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Monorepo Challenges
&lt;/h3&gt;

&lt;p&gt;Moving to a monorepo solves some problems but creates others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build Times&lt;/strong&gt;: Without smart tooling, CI runs tests on ALL modules even when you only changed one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Tracking&lt;/strong&gt;: How do you know which modules depend on each other? Manual documentation? Good luck keeping that up to date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task Orchestration&lt;/strong&gt;: Running &lt;code&gt;terraform fmt&lt;/code&gt; across 50 modules means writing bash scripts that find all the directories, loop through them, and aggregate results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Experience&lt;/strong&gt;: Developers need clear visibility into what's affected by their changes and confidence that they're running the right tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The traditional approach to Terraform module management leaves teams choosing between the coordination overhead of multi-repo or the inefficiency of a naive monorepo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Nx for Terraform Modules?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nx.dev" rel="noopener noreferrer"&gt;Nx&lt;/a&gt; is a smart build system originally designed for JavaScript/TypeScript monorepos, but it's fundamentally &lt;strong&gt;language-agnostic&lt;/strong&gt;. At its core, Nx provides exactly what we need for managing Terraform modules:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Intelligent Task Orchestration
&lt;/h3&gt;

&lt;p&gt;Nx understands your project graph - which modules depend on which. When you run a task, Nx:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only executes tasks on &lt;strong&gt;affected&lt;/strong&gt; projects based on your git diff&lt;/li&gt;
&lt;li&gt;Runs tasks in the &lt;strong&gt;correct order&lt;/strong&gt; based on dependencies&lt;/li&gt;
&lt;li&gt;Executes independent tasks in &lt;strong&gt;parallel&lt;/strong&gt; to maximize speed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Terraform, this means running &lt;code&gt;terraform validate&lt;/code&gt; only on the modules that changed, and their dependents - not the entire monorepo.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Dependency Graph Visualization
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;nx graph&lt;/code&gt; and see a visual representation of how your modules relate to each other. This is invaluable for understanding impact and planning refactors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futb81pxit5fakkjqnm5k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futb81pxit5fakkjqnm5k.png" alt="Example of Dependency Graph made with NX for this usecase" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Code Generation
&lt;/h3&gt;

&lt;p&gt;Nx generators let you scaffold new modules with consistent structure. Want a new Terraform module with the right directory structure, a &lt;code&gt;project.json&lt;/code&gt;, tests, and documentation? One command.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Consistent Tooling Across Projects
&lt;/h3&gt;

&lt;p&gt;Define tasks once, inherit them across all modules. Every module gets &lt;code&gt;fmt&lt;/code&gt;, &lt;code&gt;validate&lt;/code&gt;, &lt;code&gt;lint&lt;/code&gt;, and &lt;code&gt;test&lt;/code&gt; targets without copying configuration files everywhere.&lt;/p&gt;

&lt;p&gt;The beauty of Nx is that it doesn't change how you write Terraform - it enhances how you &lt;strong&gt;manage&lt;/strong&gt; Terraform modules at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up an Nx Workspace for Terraform
&lt;/h2&gt;

&lt;p&gt;Let's build a practical Nx workspace for managing Terraform modules. I'll walk you through the setup step by step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Workspace
&lt;/h3&gt;

&lt;p&gt;First, create a new Nx workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-nx-workspace@latest terraform-modules &lt;span class="nt"&gt;--preset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;npm
&lt;span class="nb"&gt;cd &lt;/span&gt;terraform-modules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose the "npm" preset - we're not building a JavaScript application, we just want Nx's task orchestration capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workspace Structure
&lt;/h3&gt;

&lt;p&gt;Here's a recommended structure for organizing Terraform modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform-modules/
├── nx.json
├── package.json
├── modules/
│   ├── scw-vpc/
│   │   ├── project.json
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── README.md
│   ├── scw-k8s/
│   │   ├── project.json
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── README.md
│   └── scw-database/
│       ├── project.json
│       └── ...
└── tools/
    └── scripts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each Terraform module lives in &lt;code&gt;modules/&lt;/code&gt; and has its own &lt;code&gt;project.json&lt;/code&gt; file where we define Nx tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Nx for Terraform Projects
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;project.json&lt;/code&gt; file is where Nx magic happens. Here's an example for the &lt;code&gt;scw-vpc&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../../node_modules/nx/schemas/project-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"projectType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"library"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fmt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform fmt -check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fmt-fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform fmt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"commands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform init -backend=false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform validate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"parallel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tflint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform-docs markdown . &amp;gt; README.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type:terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cloud:scaleway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layer:network"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;nx:run-commands&lt;/code&gt; executor lets us run any shell command - perfect for Terraform CLI commands.&lt;/p&gt;

&lt;p&gt;Now you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx &lt;span class="nb"&gt;fmt &lt;/span&gt;scw-vpc          &lt;span class="c"&gt;# Check formatting&lt;/span&gt;
nx validate scw-vpc     &lt;span class="c"&gt;# Validate configuration&lt;/span&gt;
nx lint scw-vpc         &lt;span class="c"&gt;# Run tflint&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; validate &lt;span class="c"&gt;# Run validate on all modules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Managing Dependencies Between Terraform Modules
&lt;/h2&gt;

&lt;p&gt;One of Nx's most powerful features is its understanding of dependencies. But Terraform modules don't use &lt;code&gt;import&lt;/code&gt; statements like JavaScript - so how does Nx know about dependencies?&lt;/p&gt;

&lt;h3&gt;
  
  
  Implicit Dependencies
&lt;/h3&gt;

&lt;p&gt;Nx can infer dependencies by analyzing your Terraform code. When &lt;code&gt;scw-k8s&lt;/code&gt; module references the &lt;code&gt;scw-vpc&lt;/code&gt; module with a relative path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../scw-vpc"&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nx can detect this relationship. Configure implicit dependencies in your root &lt;code&gt;nx.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pluginsConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@nx/dependency-checks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"checkVersionMismatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targetDefaults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^validate"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explicit Dependencies
&lt;/h3&gt;

&lt;p&gt;For more control, explicitly declare dependencies in your &lt;code&gt;project.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scw-k8s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"implicitDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Nx that &lt;code&gt;scw-k8s&lt;/code&gt; depends on &lt;code&gt;scw-vpc&lt;/code&gt;. Now when you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx validate scw-k8s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nx will first validate &lt;code&gt;scw-vpc&lt;/code&gt;, then &lt;code&gt;scw-k8s&lt;/code&gt;. And more importantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you change &lt;code&gt;scw-vpc&lt;/code&gt;, Nx knows that &lt;code&gt;scw-k8s&lt;/code&gt; is affected and will run validation on both modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualizing the Graph
&lt;/h3&gt;

&lt;p&gt;See your entire module ecosystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens an interactive browser view showing all your modules and their relationships. It's incredibly useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Onboarding new team members&lt;/li&gt;
&lt;li&gt;Planning large refactors&lt;/li&gt;
&lt;li&gt;Understanding blast radius of changes&lt;/li&gt;
&lt;li&gt;Identifying circular dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building a Task Pipeline
&lt;/h2&gt;

&lt;p&gt;Nx really shines when you build task pipelines that automatically run in the correct order. Let's create a comprehensive set of targets for our Terraform modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining Target Dependencies
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;nx.json&lt;/code&gt;, use &lt;code&gt;targetDefaults&lt;/code&gt; to create a pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targetDefaults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fmt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fmt"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"security"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;^&lt;/code&gt; prefix means "dependencies of dependencies". When you run &lt;code&gt;nx security scw-k8s&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Nx validates all modules that &lt;code&gt;scw-k8s&lt;/code&gt; depends on&lt;/li&gt;
&lt;li&gt;Then formats &lt;code&gt;scw-k8s&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then validates &lt;code&gt;scw-k8s&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then lints &lt;code&gt;scw-k8s&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Finally runs security scans on &lt;code&gt;scw-k8s&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All automatically, in parallel where possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Targets
&lt;/h3&gt;

&lt;p&gt;Here's a complete &lt;code&gt;project.json&lt;/code&gt; with all the targets you'll typically need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../../node_modules/nx/schemas/project-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"projectType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"library"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fmt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform fmt -check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fmt-fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform fmt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"commands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform init -backend=false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform validate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"parallel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"commands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tflint --init"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tflint"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"parallel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"security"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkov -d ."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform-docs markdown . &amp;gt; README.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type:terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cloud:scaleway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layer:network"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running Tasks Across Multiple Projects
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run fmt on all modules&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;fmt&lt;/span&gt;

&lt;span class="c"&gt;# Run affected validations based on git changes&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; validate,lint

&lt;span class="c"&gt;# Run tasks in specific order&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;fmt&lt;/span&gt;,validate,lint,security

&lt;span class="c"&gt;# Run on modules with specific tags&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; validate &lt;span class="nt"&gt;--projects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tag:cloud:scaleway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Code Generation and Scaffolding
&lt;/h2&gt;

&lt;p&gt;Manually creating &lt;code&gt;project.json&lt;/code&gt; files for each new module gets tedious. Nx generators solve this by scaffolding consistent module structures with a single command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Custom Generator
&lt;/h3&gt;

&lt;p&gt;Create a generator in &lt;code&gt;tools/generators/terraform-module/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tools/generators/terraform-module/index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generateFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@nx/devkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;terraformModuleGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;layer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`modules/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Generate files from templates&lt;/span&gt;
  &lt;span class="nf"&gt;generateFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;files&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Update workspace&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectConfiguration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;projectType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;library&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`type:terraform`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`cloud:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`layer:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;addProjectConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;projectConfiguration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;formatFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;terraformModuleGenerator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scaleway&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gcp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;azure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;network&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;storage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;security&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloud&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;layer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Template Files
&lt;/h3&gt;

&lt;p&gt;Create templates in &lt;code&gt;tools/generators/terraform-module/files/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tools/generators/terraform-module/files/main.tf__tmpl__&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;&amp;lt;%&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt; &lt;span class="err"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/&amp;lt;%= cloud %&amp;gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Add your resources here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tools/generators/terraform-module/files/variables.tf__tmpl__&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the &amp;lt;%= name %&amp;gt; module"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tools/generators/terraform-module/files/outputs.tf__tmpl__&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ID of the created resource"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the Generator
&lt;/h3&gt;

&lt;p&gt;Now create new modules consistently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx g @nx/workspace:workspace-generator terraform-module &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;scw-function &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cloud&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;scaleway &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--layer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;compute

&lt;span class="c"&gt;# Creates:&lt;/span&gt;
&lt;span class="c"&gt;# modules/scw-function/&lt;/span&gt;
&lt;span class="c"&gt;# ├── project.json&lt;/span&gt;
&lt;span class="c"&gt;# ├── main.tf&lt;/span&gt;
&lt;span class="c"&gt;# ├── variables.tf&lt;/span&gt;
&lt;span class="c"&gt;# ├── outputs.tf&lt;/span&gt;
&lt;span class="c"&gt;# └── README.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every new module gets the same structure, targets, and tags. No copy-pasting required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Strategies
&lt;/h2&gt;

&lt;p&gt;Testing infrastructure code is critical but often overlooked. With Nx, you can build a comprehensive testing pipeline that runs efficiently across all your modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 1: Static Analysis
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;terraform fmt &amp;amp; validate&lt;/strong&gt;&lt;br&gt;
Basic syntax and semantic validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;fmt&lt;/span&gt;,validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;tflint&lt;/strong&gt;&lt;br&gt;
Catch common mistakes and enforce best practices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"commands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tflint --init"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tflint --format compact"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parallel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;checkov&lt;/strong&gt;&lt;br&gt;
Security and compliance scanning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; security  &lt;span class="c"&gt;# Runs checkov on all modules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Orchestrating Static Analysis with Nx
&lt;/h3&gt;

&lt;p&gt;The magic happens when you combine Nx's dependency graph with your validation pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Only validate modules affected by your changes&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; validate,lint &lt;span class="nt"&gt;--base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main

&lt;span class="c"&gt;# Validate a module and all its dependencies&lt;/span&gt;
nx validate scw-k8s &lt;span class="nt"&gt;--with-deps&lt;/span&gt;

&lt;span class="c"&gt;# Run security scans in parallel (independent modules)&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; security &lt;span class="nt"&gt;--parallel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3

&lt;span class="c"&gt;# Run full quality checks on affected modules&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;fmt&lt;/span&gt;,validate,lint,security &lt;span class="nt"&gt;--base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can reduce CI time from hours to minutes for large infrastructure repos.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD Integration
&lt;/h2&gt;

&lt;p&gt;The real power of Nx shows up in CI/CD. Instead of running all checks on all modules, you only run what's needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/ci.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;affected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# Important for nx affected&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;18"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.6.0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install tflint&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Derive SHAs for affected command&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nrwl/nx-set-shas@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run affected format check&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx nx affected -t fmt --base=$NX_BASE --head=$NX_HEAD&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run affected validate&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx nx affected -t validate --base=$NX_BASE --head=$NX_HEAD&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run affected lint&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx nx affected -t lint --base=$NX_BASE --head=$NX_HEAD --parallel=3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run affected security scans&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx nx affected -t security --base=$NX_BASE --head=$NX_HEAD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What This Does
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Only checks affected modules&lt;/strong&gt;: If you change &lt;code&gt;scw-vpc&lt;/code&gt;, only &lt;code&gt;scw-vpc&lt;/code&gt; and modules that depend on it run checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel execution&lt;/strong&gt;: Independent modules run in parallel (configured with &lt;code&gt;--parallel=3&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast feedback&lt;/strong&gt;: Most PRs only touch 1-2 modules, so CI completes in seconds instead of minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  GitLab CI Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;
&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;validate&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;

&lt;span class="na"&gt;.nx-affected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:18&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;NX_BASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_MERGE_REQUEST_DIFF_BASE_SHA&lt;/span&gt;
    &lt;span class="na"&gt;NX_HEAD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_SHA&lt;/span&gt;

&lt;span class="na"&gt;affected:validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.nx-affected&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npx nx affected -t validate,lint --base=$NX_BASE --head=$NX_HEAD --parallel=3&lt;/span&gt;

&lt;span class="na"&gt;affected:security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.nx-affected&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npx nx affected -t security --base=$NX_BASE --head=$NX_HEAD&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;merge_requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;In a real-world scenario with 30+ Terraform modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before Nx&lt;/strong&gt;: Every PR ran checks on all 30 modules (~15 min)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After Nx&lt;/strong&gt;: Most PRs only check 1-3 affected modules (~2 min)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Savings&lt;/strong&gt;: 87% reduction in CI time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Managing Releases with Nx
&lt;/h2&gt;

&lt;p&gt;One of the most powerful yet underrated features of Nx is its ability to manage releases in a monorepo. For Terraform modules, proper release management is crucial for versioning, changelog generation, and coordinating changes across dependent modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Nx Release
&lt;/h3&gt;

&lt;p&gt;Nx provides a built-in release system that handles versioning, changelog generation, and git tagging automatically.&lt;/p&gt;

&lt;h4&gt;
  
  
  Initial Setup
&lt;/h4&gt;

&lt;p&gt;First, configure Nx Release in your &lt;code&gt;nx.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"release"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"projects"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"modules/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"projectsRelationship"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"independent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"generatorOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"packageRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{projectRoot}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"currentVersionResolver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git-tag"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"changelog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"projectChangelogs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"createRelease"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{projectRoot}/CHANGELOG.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"renderOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"authors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"commitReferences"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"commit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"commitMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chore(release): publish {projectName} {version}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a Release
&lt;/h3&gt;

&lt;p&gt;When you're ready to release modules that have changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See which modules have changed since last release&lt;/span&gt;
nx release &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# Release only affected modules with automatic version bump&lt;/span&gt;
nx release &lt;span class="nt"&gt;--projects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tag:cloud:scaleway

&lt;span class="c"&gt;# Release a specific module with a specific version&lt;/span&gt;
nx release scw-vpc &lt;span class="nt"&gt;--specifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automated Semantic Versioning
&lt;/h3&gt;

&lt;p&gt;Nx can automatically determine version bumps based on conventional commits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Patch version (fix: commits)&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix(scw-vpc): correct subnet CIDR calculation"&lt;/span&gt;

&lt;span class="c"&gt;# Minor version (feat: commits)&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat(scw-k8s): add autoscaling support"&lt;/span&gt;

&lt;span class="c"&gt;# Major version (BREAKING CHANGE: in commit body)&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat(scw-vpc)!: redesign network architecture

BREAKING CHANGE: VPC module now requires new subnet configuration"&lt;/span&gt;

&lt;span class="c"&gt;# Run release&lt;/span&gt;
nx release &lt;span class="nt"&gt;--projects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tag:cloud:scaleway &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;span class="c"&gt;# Nx will suggest: scw-vpc: 1.2.3 → 2.0.0, scw-k8s: 1.5.0 → 1.6.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Changelog Generation
&lt;/h3&gt;

&lt;p&gt;Nx automatically generates changelogs for each module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# modules/scw-vpc/CHANGELOG.md&lt;/span&gt;

&lt;span class="gu"&gt;## 2.0.0 (2025-11-01)&lt;/span&gt;

&lt;span class="gu"&gt;### ⚠ BREAKING CHANGES&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**scw-vpc:**&lt;/span&gt; VPC module now requires new subnet configuration

&lt;span class="gu"&gt;### Features&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**scw-vpc:**&lt;/span&gt; redesign network architecture (&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;a1b2c3d&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://github.com/org/repo/commit/a1b2c3d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;)

&lt;span class="gu"&gt;### Authors&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Antoine Caron (@Slashgear)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Release Workflow in CI
&lt;/h3&gt;

&lt;p&gt;Automate releases with GitHub Actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/release.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;18"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure Git&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name "github-actions[bot]"&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email "github-actions[bot]@users.noreply.github.com"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npx nx release --skip-publish&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tagging Strategy
&lt;/h3&gt;

&lt;p&gt;Nx creates git tags for each module release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Tags created by Nx&lt;/span&gt;
scw-vpc@2.0.0
scw-k8s@1.6.0
scw-database@1.3.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;External consumers can reference specific versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Using git tags to reference specific module versions&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://github.com/org/terraform-modules.git//modules/scw-vpc?ref=scw-vpc@2.0.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Independent vs Fixed Versioning
&lt;/h3&gt;

&lt;p&gt;Nx supports two release strategies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Independent&lt;/strong&gt; (recommended for Terraform modules):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each module has its own version&lt;/li&gt;
&lt;li&gt;Modules only bump when they change&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"projectsRelationship": "independent"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fixed&lt;/strong&gt; (useful for tightly coupled modules):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All modules share the same version&lt;/li&gt;
&lt;li&gt;All versions bump together even if unchanged&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"projectsRelationship": "fixed"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic versioning&lt;/strong&gt;: Based on conventional commits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated changelogs&lt;/strong&gt;: No manual CHANGELOG.md updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git tagging&lt;/strong&gt;: Automatic and consistent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Affected awareness&lt;/strong&gt;: Only release modules that changed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub releases&lt;/strong&gt;: Automatic release creation with notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes managing dozens of Terraform modules significantly less painful than manual versioning and tagging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Case
&lt;/h2&gt;

&lt;p&gt;Let's walk through a concrete example: a SaaS company managing their Scaleway infrastructure with Nx. This example is based on the &lt;a href="https://github.com/Slashgear/nx-terraform-demo" rel="noopener noreferrer"&gt;nx-terraform-demo repository&lt;/a&gt;, which you can clone and explore yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Setup
&lt;/h3&gt;

&lt;p&gt;They have the following modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scw-vpc&lt;/code&gt; - Private network configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scw-k8s&lt;/code&gt; - Kubernetes Kapsule cluster (depends on vpc)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scw-database&lt;/code&gt; - Managed PostgreSQL (depends on vpc)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scw-object-storage&lt;/code&gt; - S3-compatible object storage (independent)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scw-registry&lt;/code&gt; - Container registry (depends on vpc)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scw-loadbalancer&lt;/code&gt; - Load balancer (depends on vpc, k8s)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scw-monitoring&lt;/code&gt; - Observability stack (depends on k8s)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Dependency Graph
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scw-vpc
  ├── scw-k8s
  │   ├── scw-loadbalancer
  │   └── scw-monitoring
  ├── scw-database
  └── scw-registry

scw-object-storage (independent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;nx graph&lt;/code&gt; to visualize this automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario: Updating the VPC Module
&lt;/h3&gt;

&lt;p&gt;A developer needs to add a new subnet to &lt;code&gt;scw-vpc&lt;/code&gt;. Here's what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Developer makes changes to scw-vpc&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feat/add-subnet
&lt;span class="c"&gt;# ... edit scw-vpc/main.tf ...&lt;/span&gt;

&lt;span class="c"&gt;# Check what's affected&lt;/span&gt;
nx show projects &lt;span class="nt"&gt;--affected&lt;/span&gt;
&lt;span class="c"&gt;# Output: scw-vpc, scw-k8s, scw-database, scw-registry, scw-loadbalancer, scw-monitoring&lt;/span&gt;

&lt;span class="c"&gt;# Run validation only on affected modules&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; validate,lint
&lt;span class="c"&gt;# Validates: vpc → k8s, database, registry → loadbalancer, monitoring&lt;/span&gt;

&lt;span class="c"&gt;# Push and CI runs affected checks&lt;/span&gt;
&lt;span class="c"&gt;# Only the 6 affected modules are validated, not all 7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Instead of running checks on all modules, only the affected ones run. The independent module (&lt;code&gt;scw-object-storage&lt;/code&gt;) is skipped entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario: Adding New Feature to Monitoring
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Developer updates scw-monitoring&lt;/span&gt;
nx show projects &lt;span class="nt"&gt;--affected&lt;/span&gt;
&lt;span class="c"&gt;# Output: scw-monitoring (only!)&lt;/span&gt;

&lt;span class="c"&gt;# CI only validates this one module&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; validate,lint,security &lt;span class="nt"&gt;--base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: All other 6 modules skipped, massive time savings.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Impact
&lt;/h3&gt;

&lt;p&gt;Before Nx:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every PR: validate all 7 modules, run all checks (~20 min)&lt;/li&gt;
&lt;li&gt;Developers waited, context-switched, lost productivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After Nx:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small changes: 1-2 modules affected (~3 min)&lt;/li&gt;
&lt;li&gt;VPC changes: 6 modules affected (~8 min)&lt;/li&gt;
&lt;li&gt;Clear visibility with &lt;code&gt;nx graph&lt;/code&gt; on what breaks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;p&gt;After implementing Nx with Terraform modules across multiple teams, here are the lessons learned:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Use Meaningful Tags
&lt;/h3&gt;

&lt;p&gt;Tags enable powerful filtering. Be strategic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"type:terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;All&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;terraform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;projects&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"cloud:scaleway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Cloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;provider&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"layer:network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Infrastructure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;layer&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"team:platform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Owning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;team&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"env:multi"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Supports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;multiple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;envs&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run targeted commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; validate &lt;span class="nt"&gt;--projects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tag:team:platform
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; security &lt;span class="nt"&gt;--projects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tag:cloud:scaleway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Keep Modules Small and Focused
&lt;/h3&gt;

&lt;p&gt;Each module should do one thing well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Good: &lt;code&gt;scw-vpc&lt;/code&gt;, &lt;code&gt;scw-k8s&lt;/code&gt;, &lt;code&gt;scw-database&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;❌ Bad: &lt;code&gt;scw-full-infrastructure&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small modules = better reusability and easier testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use Target Dependencies Wisely
&lt;/h3&gt;

&lt;p&gt;Define a clear pipeline in &lt;code&gt;nx.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targetDefaults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fmt"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures tasks run in the right order automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Document with terraform-docs
&lt;/h3&gt;

&lt;p&gt;Automate documentation generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx:run-commands"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform-docs markdown table --output-file README.md ."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modules/scw-vpc"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;nx run-many -t docs&lt;/code&gt; to update all module docs at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Implement Pre-commit Hooks
&lt;/h3&gt;

&lt;p&gt;Use Husky to run quick checks before commits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx husky &lt;span class="nb"&gt;install
&lt;/span&gt;npx husky add .husky/pre-commit &lt;span class="s2"&gt;"npx nx affected -t fmt-fix,validate"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations and Considerations
&lt;/h2&gt;

&lt;p&gt;Nx is powerful, but it's not a silver bullet. Here are honest tradeoffs to consider:&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning Curve
&lt;/h3&gt;

&lt;p&gt;Your team needs to learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nx concepts (projects, targets, affected commands)&lt;/li&gt;
&lt;li&gt;Node.js/npm basics (even though you're not writing JavaScript)&lt;/li&gt;
&lt;li&gt;How to write &lt;code&gt;project.json&lt;/code&gt; files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;: Start with 3-5 modules, prove the value, then expand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js Dependency
&lt;/h3&gt;

&lt;p&gt;You now need Node.js in your infrastructure workflows. Some teams find this odd.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Counter-argument&lt;/strong&gt;: Node.js is ubiquitous, lightweight, and your CI already has it. The benefits far outweigh this concern.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Management Unchanged
&lt;/h3&gt;

&lt;p&gt;Nx doesn't help with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform state files&lt;/li&gt;
&lt;li&gt;State locking&lt;/li&gt;
&lt;li&gt;Remote backends&lt;/li&gt;
&lt;li&gt;Workspace management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You still need proper Terraform state hygiene. Nx is purely about &lt;strong&gt;workspace organization&lt;/strong&gt; and &lt;strong&gt;task orchestration&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Ideal for Small Projects
&lt;/h3&gt;

&lt;p&gt;If you have 1-3 Terraform modules that rarely change, Nx is overkill. The overhead isn't worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Nx when&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5+ modules&lt;/li&gt;
&lt;li&gt;Frequent changes&lt;/li&gt;
&lt;li&gt;Multiple team members&lt;/li&gt;
&lt;li&gt;Complex dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Module Source References
&lt;/h3&gt;

&lt;p&gt;Modules in the monorepo use relative paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../scw-vpc"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;External consumers need a different approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish to a module registry&lt;/li&gt;
&lt;li&gt;Use git tags with specific refs&lt;/li&gt;
&lt;li&gt;Or keep internal modules internal&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When NOT to Use This Approach
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single module projects&lt;/strong&gt;: Just use plain Terraform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completely independent modules&lt;/strong&gt;: If modules never interact, separate repos might be simpler&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team resistant to change&lt;/strong&gt;: Cultural fit matters more than technical benefits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be pragmatic. Nx solves specific problems - make sure you have those problems first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Managing Terraform modules at scale doesn't have to be painful. Nx brings proven monorepo practices from the frontend world to infrastructure code, offering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent task orchestration&lt;/strong&gt; - Run only what's affected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency awareness&lt;/strong&gt; - Understand module relationships automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster CI/CD&lt;/strong&gt; - 80%+ time savings in real-world scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better DX&lt;/strong&gt; - Consistent tooling, code generation, and clear workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The initial setup requires investment - learning Nx concepts, creating &lt;code&gt;project.json&lt;/code&gt; files, updating CI workflows. But for teams managing 5+ Terraform modules with frequent changes, the ROI is substantial.&lt;/p&gt;

&lt;p&gt;Start small: pick 3-5 modules, prove the concept, measure the impact. If it works for you (and it likely will), expand to your full infrastructure codebase.&lt;/p&gt;

&lt;p&gt;The future of infrastructure management is smart, graph-aware tooling. Nx brings us closer to that future today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Demo Repository&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Slashgear/nx-terraform-demo" rel="noopener noreferrer"&gt;nx-terraform-demo&lt;/a&gt; - Complete working example with 7 Scaleway Terraform modules managed with Nx&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nx Documentation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nx.dev" rel="noopener noreferrer"&gt;Nx Official Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nx.dev/nx-api/nx/executors/run-commands" rel="noopener noreferrer"&gt;nx:run-commands Executor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nx.dev/extending-nx/recipes/local-generators" rel="noopener noreferrer"&gt;Creating Custom Generators&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Terraform Tooling&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://terraform-docs.io/" rel="noopener noreferrer"&gt;terraform-docs&lt;/a&gt; - Automated documentation generation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/terraform-linters/tflint" rel="noopener noreferrer"&gt;tflint&lt;/a&gt; - Terraform linter&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.checkov.io/" rel="noopener noreferrer"&gt;checkov&lt;/a&gt; - Security and compliance scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scaleway Provider&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/scaleway/scaleway/latest/docs" rel="noopener noreferrer"&gt;Scaleway Terraform Provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.scaleway.com/en/docs/" rel="noopener noreferrer"&gt;Scaleway Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://monorepo.tools/" rel="noopener noreferrer"&gt;Monorepo Tools&lt;/a&gt; - Comparison of monorepo solutions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.terraform-best-practices.com/" rel="noopener noreferrer"&gt;Terraform Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you tried managing Terraform with Nx? I'd love to hear about your experience. Find me on &lt;a href="https://bsky.app/profile/slashgear.dev" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt; or &lt;a href="https://github.com/Slashgear" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>nx</category>
      <category>devops</category>
      <category>terraform</category>
      <category>monorepo</category>
    </item>
    <item>
      <title>How to automatically merge dependabot pull requests with Github Actions ?</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Tue, 10 May 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/slashgear_/how-to-automatically-merge-dependabot-pull-requests-with-github-actions--30pe</link>
      <guid>https://dev.to/slashgear_/how-to-automatically-merge-dependabot-pull-requests-with-github-actions--30pe</guid>
      <description>&lt;p&gt;Recently, I’ve been working a lot on industrializing this blog &lt;em&gt;(maybe even a little too much)&lt;/em&gt;. This blog is my playground, I experiment, I play, I test things. I admit that it’s over-engineered but I’m having a lot of fun with it. 😅&lt;/p&gt;

&lt;p&gt;Since its release, I tried to use &lt;a href="https://docs.github.com/en/code-security/dependabot" rel="noopener noreferrer"&gt;dependabot&lt;/a&gt; from github on my repositories. At the beginning without much conviction. As much as I thought it was great in principle, the noise generated by the dozens of open PRs per day was really annoying. As a maintainer of various open-source projects, I have been victim of dependabot PR flood several times. &lt;em&gt;A real nightmare&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I had foolishly made no effort to configure it. Recently, I thought I should give it another chance. So I set up &lt;a href="https://github.com/Slashgear/slashgear.github.io/blob/72f2575bcc4c9eedf4c61a7cf734e54eceee1241/.github/dependabot.yml" rel="noopener noreferrer"&gt;this configuration that allowed me to update my JS dependencies and used github actions&lt;/a&gt; in my workflows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt; &lt;span class="c1"&gt;# See documentation for possible values&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/'&lt;/span&gt; &lt;span class="c1"&gt;# Location of package manifests&lt;/span&gt;
    &lt;span class="na"&gt;open-pull-requests-limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;daily'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;github-actions'&lt;/span&gt; &lt;span class="c1"&gt;# See documentation for possible values&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/'&lt;/span&gt; &lt;span class="c1"&gt;# Location of package manifests&lt;/span&gt;
    &lt;span class="na"&gt;open-pull-requests-limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;daily'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No lie, with this configuration, on this blog developed with Gatsby, &lt;a href="https://docs.github.com/en/code-security/dependabot" rel="noopener noreferrer"&gt;Dependabot&lt;/a&gt; was opening up to 20 Pull Requests per week.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Limits
&lt;/h2&gt;

&lt;p&gt;At the beginning, when you see all the PRs opening with all the dependencies updates, you are quite happy. However, managing this every week is really time consuming. Moreover, when we merge these PRs in a chain, we generate in cascade conflicts on the &lt;code&gt;package.json&lt;/code&gt; file which prevents the merge 😒. It is therefore often necessary to wait for dependabot to rebase the PR by itself, for the tests to pass and then wait for the Pull Request to merge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fblog.slashgear.dev%2F8483cc022eacd31176dd8f0659c3d658%2Fclick.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fblog.slashgear.dev%2F8483cc022eacd31176dd8f0659c3d658%2Fclick.gif" alt="Animation of a hand clicking frantically" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ♻️ Let’s merge it automatically!
&lt;/h2&gt;

&lt;p&gt;Having set up E2E tests on this blog, the check ✅ given by my continuous integration workflow is very reassuring.&lt;a href="https://blog.slashgear.dev/how-to-setup-e2e-tests-with-webdriverio/" rel="noopener noreferrer"&gt;You can have a look at this article which describes how I set up these tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this automation, I’m pretty serene when I merge the dependabot update PR. So I thought, &lt;em&gt;“Why not automatically merge the PRs that pass the E2E tests?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To do this, in my continuous integration workflow, I just had to add a new job configured this way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="na"&gt;dependabot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Dependabot'&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;e2e&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# After the E2E and build jobs, if one of them fails, it won't merge the PR.&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor == 'dependabot[bot]' &amp;amp;&amp;amp; github.event_name == 'pull_request'}}&lt;/span&gt; &lt;span class="c1"&gt;# Detect that the PR author is dependabot&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Enable auto-merge for Dependabot PRs&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gh pr merge --auto --merge "$PR_URL"&lt;/span&gt; &lt;span class="c1"&gt;# Use Github CLI to merge automatically the PR&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;PR_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.event.pull_request.html_url}}&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GITHUB_TOKEN}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/Slashgear/slashgear.github.io/blob/72f2575bcc4c9eedf4c61a7cf734e54eceee1241/.github/workflows/continuous-integration.yml" rel="noopener noreferrer"&gt;If you want to see the real example, take a look at this blog workflow.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  👮‍ In the end, is it really safer?
&lt;/h2&gt;

&lt;p&gt;SWithout explaining what &lt;a href="https://docs.github.com/en/code-security/dependabot" rel="noopener noreferrer"&gt;dependabot&lt;/a&gt; is, one could think that all this could bring more security. Indeed, as soon as security patches are available, they would be automatically applied and deployed on my blog without any manual manipulation being necessary.&lt;/p&gt;

&lt;p&gt;My availability to merge the PRs of dependabot being limited, one could say that setting up automation improves things. A good E2E testing strategy ensuring that I don’t break anything too important on my site seems really convenient.&lt;/p&gt;

&lt;p&gt;But in the end, who controls the updates and what they contain? Who makes sure that they don’t contain malicious code? This blog is not a sandbox for me, so it’s not very problematic. So think carefully before setting it up.&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Best practices for Web application maintenance</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Sun, 05 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/bedrock/best-practices-for-web-application-maintenance-2hgl</link>
      <guid>https://dev.to/bedrock/best-practices-for-web-application-maintenance-2hgl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;How not to throw away your application every two years?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Feedback based on best practices applied to the web platform developed at &lt;em&gt;&lt;a href="https://www.bedrockstreaming.com/" rel="noopener noreferrer"&gt;Bedrock Streaming&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A bit of context
&lt;/h2&gt;

&lt;p&gt;At Bedrock Streaming many teams develop and maintain &lt;em&gt;frontend&lt;/em&gt; applications for our customers and users. Some of those application are not very young. In fact, the application I’m mainly working on is a website whose developments started in 2014. I have already mentioned it in different articles of &lt;a href="https://slashgear.github.io/" rel="noopener noreferrer"&gt;this blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29uchi6ycdljaxu6y4gf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29uchi6ycdljaxu6y4gf.png" alt="screenshot of the number of commits on master of our project 15668" width="294" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might think: “Oh poor people, maintaining an almost 10 year old application must be hell!”&lt;/p&gt;

&lt;p&gt;Don’t worry, it’s not the case! I have worked on projects that are much less old but where the development of new features was much more painful.&lt;/p&gt;

&lt;p&gt;Today the project is technically up to date, we must be on the latest version of React while it had started on a version &lt;em&gt;0.x.x&lt;/em&gt;. In this world of web technologies often criticized (eg: the many articles on the &lt;em&gt;Javascript Fatigue&lt;/em&gt;) whose tools and practices are constantly evolving, keeping a project “up to date” remains a real challenge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsvckhjbg2tatv7daqfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsvckhjbg2tatv7daqfm.png" alt="number of versions of the application 1445" width="350" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moreover, in the context of this project, in almost 10 years, we have had about 100 contributors. Some have only stayed a few months/years. How can we keep the maximum knowledge on “How we do things and how it works?” in such a moving human context?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3df7n6n27wsu2vw7mp8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3df7n6n27wsu2vw7mp8.png" alt="list of the 100 contributors of the project" width="690" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what I would like to present you.&lt;/p&gt;

&lt;p&gt;With the help of my colleagues, I have collected the list of good practices that still allow us to maintain this project today. With &lt;a href="https://twitter.com/fooragnak" rel="noopener noreferrer"&gt;Florent Dubost&lt;/a&gt;, we often thought that it would be interesting to publish it. We hope you will find it useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set rules and automate them
&lt;/h2&gt;

&lt;p&gt;A project that stands the test of time is first and foremost a set of knowledge that is stacked one on top of the other. It’s like the Kapla tower you used to build as a child, trying to get as high as possible. A solid base on which we hope to add as much as possible before a potential fall.&lt;/p&gt;

&lt;p&gt;From the beginning of a project, we have to make important decisions about “How do we want to do things? We think for example about “What format for our files? How do we name this or that thing?” Writing accurate documentation of “How we do things” might seem like a good idea.&lt;/p&gt;

&lt;p&gt;However, documentation is cool, but it tends to get outdated very quickly. Our decisions evolve, but documentation does not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Times change but not READMEs.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/omansour" rel="noopener noreferrer"&gt;&lt;em&gt;Olivier Mansour (deputy CTO at Bedrock)&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Automating the checking of each of the rules we impose on ourselves (on our codebase or our processes) is much more durable. To make it simple, we avoid as much as possible to say “We should do things like that”, and we prefer “we’ll code something that checks it for us”. On top of that, on the JS side we are really well equipped with tools like &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;Eslint&lt;/a&gt; that allow us to implement our own rules.&lt;/p&gt;

&lt;p&gt;So the reflex we try to adopt is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“We should try to do it like this now!”&lt;/li&gt;
&lt;li&gt;“Ok that’s interesting, but how can we make sure we do it like that automatically with our CI (Continuous Integration)?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Continuous Integration of a project is the perfect solution to not miss anything on every &lt;em&gt;Pull Request&lt;/em&gt; we provide. Reviews are only easier because you don’t have to worry about all the rules that are already automated. In this model, the review is more for knowledge sharing than for typo copying and other non-compliance with the project conventions.&lt;/p&gt;

&lt;p&gt;In this principle, we must therefore try to banish oral rules. The time of the druids is over, if all the good practices of a project have to be transmitted orally, it will only take longer to guide new developers into your team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pgsd2t3t620aivnba6e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pgsd2t3t620aivnba6e.gif" alt="the recipe of the magic potion of panoramix is lost because secret" width="518" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A project is not set in stone. These rules evolve with time. It is therefore preferable to add rules that have a script that will &lt;em&gt;autofix&lt;/em&gt; the whole codebase intelligently. Many Eslint rules offer this, and it is a very important selection criteria when choosing new conventions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eslint --fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A very strict rule that will force you to modify your code manually before each push is annoying in the long run and will annoy your teams. Whereas a rule (even a very strict one) that can auto-fix itself at commit time will not be seen as annoying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to decide to add new rules ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This question may seem thorny, take for example the case of &lt;code&gt;&amp;lt;tab&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;space&amp;gt;&lt;/code&gt; in files. For this, we try to avoid the endless debates and follow the trend and rules of the community. For example, &lt;a href="https://github.com/M6Web/eslint-tools" rel="noopener noreferrer"&gt;our Eslint configuration base&lt;/a&gt; is based on Airbnb’s which seems to have some success in the JS community. But if the rule we want to impose on ourselves is not available in Eslint or other tools, we sometimes prefer not to follow the rule rather than say “We’ll do it without a checking CI”.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;em&gt;almost&lt;/em&gt; exhaustive list 🤞
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gz64al4km1bmm6xf3s4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gz64al4km1bmm6xf3s4.png" alt="Our continuous integration workflow" width="768" height="896"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The file format is tracked managed by &lt;a href="https://editorconfig.org/" rel="noopener noreferrer"&gt;Editorconfig&lt;/a&gt;, &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;prettier&lt;/a&gt; and &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;Eslint&lt;/a&gt;. We have opensourced &lt;a href="https://github.com/M6Web/eslint-tools" rel="noopener noreferrer"&gt;our own configuration&lt;/a&gt;, if it is of any use to you.&lt;/li&gt;
&lt;li&gt;We use a &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/" rel="noopener noreferrer"&gt;specific commit name&lt;/a&gt; to generate our changelog. To make sure devs follow it, a simple step in our CI checks it.&lt;/li&gt;
&lt;li&gt;We don’t want a dev to make our JS bundles very big in production, so we track and measure their size in the CI. We use an in-house tool but we can recommend the [BuildTracker] tool (&lt;a href="https://buildtracker.dev/" rel="noopener noreferrer"&gt;https://buildtracker.dev/&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Test coverage is not an indicator for the team, not all lines have the same need for us to be tested. Some teams at Bedrock however follow this indicator which at least has the interest to give a trend.&lt;/li&gt;
&lt;li&gt;Our unit tests obviously run on the CI, these must pass.&lt;/li&gt;
&lt;li&gt;Our functional tests (End to end: E2E) run on Chrome Headless, they must be green.&lt;/li&gt;
&lt;li&gt;The logs of our E2E tests are retrieved and parsed to avoid errors or React warnings (the parsing script is however complicated to maintain)&lt;/li&gt;
&lt;li&gt;Functional tests run in a &lt;em&gt;sandbox&lt;/em&gt; where the whole network is proxied. We make sure that our tests do not depend on a non mocked API that could slow down their execution.&lt;/li&gt;
&lt;li&gt;During the E2E tests we check that no image request has generated a 404.&lt;/li&gt;
&lt;li&gt;We perform some &lt;a href="https://www.deque.com/axe/" rel="noopener noreferrer"&gt;accessibility checks with Axe&lt;/a&gt; during our E2E tests.&lt;/li&gt;
&lt;li&gt;We check some rules on the CSS with &lt;a href="https://stylelint.io/" rel="noopener noreferrer"&gt;Stylelint&lt;/a&gt; and &lt;a href="https://github.com/M6Web/bemlinter" rel="noopener noreferrer"&gt;bemlinter&lt;/a&gt; (we don’t use BEM anymore but there is still some style managed in SCSS that we migrate little by little in StyledComponent)&lt;/li&gt;
&lt;li&gt;The project is a monorepo on which we try to maintain the same dependencies versions for each package. For that we have developed a tool that allows to do this check &lt;em&gt;&lt;a href="https://www.npmjs.com/package/monorepo-dependencies-check" rel="noopener noreferrer"&gt;monorepo-dependencies-check&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;We check that our &lt;code&gt;yarn.lock&lt;/code&gt; file has not been inadvertently modified or that it has been updated with respect to the modifications of the &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; is used to manage our cloud resources, we check that the file format is correct.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test, test, test
&lt;/h2&gt;

&lt;p&gt;I hope that in 2021 it is no longer necessary to explain why automatic testing of your application is essential to make it sustainable. In JS, we are rather well equipped in terms of testing tools today. However, the eternal question remains:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What do we want to test?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Globally if we search on the internet this question, we see that different needs make emerge very different practices and testing tools. It would be very presumptuous to think that there is a good way to automatically test your application. This is why it is preferable to define one or more test strategies that meet defined and limited needs.&lt;/p&gt;

&lt;p&gt;Our test strategies are based on two distinct goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To automate the verification of the functionalities proposed to the users by putting ourselves in their place.&lt;/li&gt;
&lt;li&gt;To provide us with efficient solutions to specify the way we implement our technical solutions to allow us to make them evolve more easily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do this, we perform two “types of tests” that I propose to present here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our E2E tests
&lt;/h3&gt;

&lt;p&gt;We call them “functional tests”, they are End-to-end (E2E) tests on a very efficient technical stack composed of &lt;a href="https://cucumber.io/docs/installation/javascript/" rel="noopener noreferrer"&gt;CucumberJS&lt;/a&gt;, &lt;a href="https://webdriver.io/" rel="noopener noreferrer"&gt;WebdriverIO&lt;/a&gt; with &lt;a href="https://developers.google.com/web/updates/2017/04/headless-chrome" rel="noopener noreferrer"&gt;ChromeHeadless&lt;/a&gt;This is a technical stack set up at the beginning of the project (at the time with &lt;a href="https://phantomjs.org/" rel="noopener noreferrer"&gt;PhantomJS&lt;/a&gt; for the oldest among you)&lt;/p&gt;

&lt;p&gt;This stack allows us to automate the piloting of tests that control a browser. This browser will perform actions that are as close as possible to what our real users can do while checking how the site reacts.&lt;/p&gt;

&lt;p&gt;A few years ago, this technical stack was rather complicated to set up, but today it is rather simple to do.&lt;a href="https://github.com/Slashgear/slashgear.github.io" rel="noopener noreferrer"&gt;The site that hosts this blog post&lt;/a&gt; is itself proof of this. It only took me about ten minutes to set up this stack with &lt;a href="https://webdriver.io/docs/gettingstarted" rel="noopener noreferrer"&gt;the WebdriverIo CLI&lt;/a&gt; to verify that my blog is working as expected.&lt;/p&gt;

&lt;p&gt;I recently published &lt;a href="https://dev.to/slashgear_/how-to-setup-end-to-end-tests-with-webdriverio-on-github-action-f9n"&gt;an article presenting the implementation of this stack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So here is an example of an E2E test file to give you an idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kd"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Playground

  &lt;span class="kn"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Playground context
    &lt;span class="nf"&gt;Given &lt;/span&gt;I use &lt;span class="s"&gt;"playground"&lt;/span&gt; test context

  &lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Check if playground is reachable
    &lt;span class="nf"&gt;When &lt;/span&gt;As user &lt;span class="s"&gt;"toto@toto.fr"&lt;/span&gt; I visit the &lt;span class="s"&gt;"playground"&lt;/span&gt; page
    &lt;span class="nf"&gt;And &lt;/span&gt;I click on &lt;span class="s"&gt;"playground trigger"&lt;/span&gt;
    &lt;span class="nf"&gt;Then &lt;/span&gt;I should see a &lt;span class="s"&gt;"visible playground"&lt;/span&gt;
    &lt;span class="nf"&gt;And &lt;/span&gt;I should see 4 &lt;span class="s"&gt;"playground tab"&lt;/span&gt; in &lt;span class="s"&gt;"playground"&lt;/span&gt;

    &lt;span class="nf"&gt;When &lt;/span&gt;I click on &lt;span class="s"&gt;"playground trigger"&lt;/span&gt;
    &lt;span class="nf"&gt;Then &lt;/span&gt;I should not see a &lt;span class="s"&gt;"visible playground"&lt;/span&gt;

    &lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it looks like this in local with my Chrome browser!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx52jbcyoxk2eqdjcmyav.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx52jbcyoxk2eqdjcmyav.gif" alt="Example of functional test execution" width="690" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a diagram that explains how this stack works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gwupuk6jz3ipa3u0e8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gwupuk6jz3ipa3u0e8z.png" alt="diagram that explains how our stack works" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, Bedrock’s web application has over 800 E2E test cases running on each of our &lt;em&gt;Pull Request&lt;/em&gt; and the &lt;code&gt;master&lt;/code&gt; branch. They assure us that we are not introducing any functional regression and that’s just great!&lt;/p&gt;

&lt;p&gt;👍 The positives&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebdriverIO also allows us to run these same tests on real devices on a daily basis through the paid SAAS service &lt;a href="https://www.browserstack.com/" rel="noopener noreferrer"&gt;Browserstack&lt;/a&gt;. So we have every day a &lt;em&gt;job&lt;/em&gt; that makes sure that our site works correctly on a Chrome last version on Windows 10 and Safari on MacOs.&lt;/li&gt;
&lt;li&gt;These tests allow us to easily document the functionality of the application using the Gherkin language.&lt;/li&gt;
&lt;li&gt;They allow us to reproduce cases that are far from nominal. In a &lt;em&gt;TDD&lt;/em&gt; logic, they allow us to advance on the development without having to click for hours.&lt;/li&gt;
&lt;li&gt;These tests allowed us not to break the old version of the site which is still in production for some customers while our efforts are concentrated on the new one.&lt;/li&gt;
&lt;li&gt;They give us real confidence.&lt;/li&gt;
&lt;li&gt;Thanks to our library &lt;a href="https://www.npmjs.com/package/superagent-mock" rel="noopener noreferrer"&gt;&lt;em&gt;superagent-mock&lt;/em&gt;&lt;/a&gt;, we can &lt;em&gt;fixturer&lt;/em&gt; (plug, mock) all the APIs we depend on and thus even check the error cases. Also, mocking the browser’s XHR layer allows for a significant improvement in test execution time. 🚀&lt;/li&gt;
&lt;li&gt;They give us access to extended uses like:

&lt;ul&gt;
&lt;li&gt;checking accessibility rules&lt;/li&gt;
&lt;li&gt;check the browser console logs (to avoid introducing errors or React Warning for example)&lt;/li&gt;
&lt;li&gt;monitoring all network calls of the site through a proxy&lt;/li&gt;
&lt;li&gt;and so on…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👎 The complications&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintaining this stack is complicated and expensive. Since few resources are published on this domain, we sometimes find ourselves digging for days to fix them 😅. Sometimes we feel quite alone in having these worries.&lt;/li&gt;
&lt;li&gt;It is very easy to code a so-called &lt;em&gt;flaky&lt;/em&gt; E2E test (ie: a test that can fail randomly). They make us think that something is broken. They sometimes take us a long time to stabilize. It is still &lt;strong&gt;much better to remove a test that will not give you a stable result.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Running all the tests takes a lot of time on our continuous integration. We must regularly work on their optimization so that the feedback they provide you is as fast as possible. These important times also cost money, because we have to run these tests on machines. For your information, the infrastructure of the website (just the hosting of our Node servers + static files + CDN) cost much less than our continuous integration. This obviously makes our Ops team smile! 😊&lt;/li&gt;
&lt;li&gt;The new recruits in our teams have often never done this kind of testing, so there is a &lt;del&gt;struggle&lt;/del&gt; phase of learning…&lt;/li&gt;
&lt;li&gt;Some features are sometimes too complicated to test with our E2E stack (for example, payment paths that depend on third parties). So we sometimes fall back on other techniques with Jest, especially with a less unitary scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Our “unit” tests
&lt;/h3&gt;

&lt;p&gt;To complete our functional tests we also have a stack of tests written with &lt;a href="https://jestjs.io/en/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt;. We call these tests unit tests because we have as a principle to try to always test our JS modules independently from the others.&lt;/p&gt;

&lt;p&gt;Let’s not debate here about “Are these real unit tests?”, there are enough articles on the internet about this topic.&lt;/p&gt;

&lt;p&gt;We use these tests for different reasons that cover needs that our functional tests do not cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to help us develop our JS modules with TDD practices.&lt;/li&gt;
&lt;li&gt;to document and describe how a JS module works.&lt;/li&gt;
&lt;li&gt;test very/too complicated edge cases with our E2E tests.&lt;/li&gt;
&lt;li&gt;facilitate the refactoring of our application by showing us the technical impacts of our modifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these tests, we put ourselves at the level of a utility function, a Redux action, a reducer, a React component. We rely mainly on &lt;a href="https://dev.to/slashgear_/discover-jest-hidden-feature-automock-43i0-temp-slug-9168332"&gt;the &lt;code&gt;automock&lt;/code&gt; functionality of Jest&lt;/a&gt; which allows us to isolate our JS modules when we test.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq1lhc2748xg9ts1nwzw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq1lhc2748xg9ts1nwzw.jpg" alt="visual representation of the automock" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The previous image represents the metaphor that allows us to explain our unit testing strategy to newcomers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You have to imagine that the application is a wall made of unit bricks (our ecmascript modules), our unit tests must test one by one the bricks in total independence from the others. Our functional tests are there to test the cement between the bricks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;To summarize, we could say that our E2E tests test &lt;em&gt;what our application should do&lt;/em&gt;, and our unit tests make sure to check &lt;em&gt;how it works.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today there are more than 6000 unit tests that cover the application and allow to limit regressions.&lt;/p&gt;

&lt;p&gt;👍&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://jestjs.io/en/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; is really a great library, fast, complete, well documented.&lt;/li&gt;
&lt;li&gt;Unit tests help us a lot to understand &lt;em&gt;several years later&lt;/em&gt; how it all works.&lt;/li&gt;
&lt;li&gt;We always manage to unit test our code, and it complements our E2E tests well.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;automock&lt;/code&gt; is really handy for breaking down tests by modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👎&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sometimes we found ourselves limited by our E2E test stack and couldn’t rely solely on unit tests. We were missing something to be able to make sure that the &lt;em&gt;cement between the bricks&lt;/em&gt; worked as we wanted it to. For this, a second test stack &lt;a href="https://jestjs.io/en/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; was set up called “integration test” where the &lt;code&gt;automock&lt;/code&gt; is disabled.&lt;/li&gt;
&lt;li&gt;The abuse of &lt;a href="https://jestjs.io/docs/snapshot-testing" rel="noopener noreferrer"&gt;&lt;em&gt;Snapshot&lt;/em&gt;&lt;/a&gt; is dangerous for your health. The use of &lt;em&gt;“Snapshot testing”&lt;/em&gt; can save time on the implementation of your tests but can reduce the quality. Having to review a 50 line object in &lt;em&gt;Snapshot&lt;/em&gt; is neither easy nor relevant.&lt;/li&gt;
&lt;li&gt;With the depreciation of &lt;a href="https://enzymejs.github.io/enzyme/" rel="noopener noreferrer"&gt;EnzymeJS&lt;/a&gt;, we are forced to migrate to &lt;a href="https://testing-library.com/docs/react-testing-library/intro/" rel="noopener noreferrer"&gt;React Testing Library&lt;/a&gt;. It is of course possible to unit test components with this new library. Unfortunately, this is not really the spirit and the way to do it.&lt;a href="https://testing-library.com/docs/react-testing-library/intro/" rel="noopener noreferrer"&gt;React Testing Library&lt;/a&gt; pushes us &lt;a href="https://kentcdodds.com/blog/why-i-never-use-shallow-rendering" rel="noopener noreferrer"&gt;not to play with &lt;em&gt;shallow rendering&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Our principles
&lt;/h3&gt;

&lt;p&gt;We try to always follow the following rules when asking the question “Should I add tests?“.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If our &lt;em&gt;Pull Request&lt;/em&gt; introduces new user features, we need to integrate E2E test scenarios. Unit tests with Jest can complete / replace them accordingly.&lt;/li&gt;
&lt;li&gt;If our &lt;em&gt;Pull Request&lt;/em&gt; aims to fix a bug, it means that we are missing a test case. We must therefore try to add an E2E test or, failing that, a unit test.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;It is while writing these lines that I think that these principles could very well be automated.&lt;/em&gt; 🤣&lt;/p&gt;

&lt;h2&gt;
  
  
  The project stays, the features don’t
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“The second evolution of a feature is very often its removal.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a matter of principle, we want to make sure that every new feature in the application does not base its activation on simply being in the codebase. Typically, the lifecycle of a feature in a project can be as follows (in a &lt;a href="https://guides.github.com/introduction/flow/" rel="noopener noreferrer"&gt;Github Flow&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a person implements on a branch&lt;/li&gt;
&lt;li&gt;the feature is &lt;em&gt;merged&lt;/em&gt; on master&lt;/li&gt;
&lt;li&gt;it is deployed in production&lt;/li&gt;
&lt;li&gt;lives its feature life (sometimes with bugs and fixes)&lt;/li&gt;
&lt;li&gt;the feature is not needed anymore&lt;/li&gt;
&lt;li&gt;a person unravels the code and removes it&lt;/li&gt;
&lt;li&gt;new deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To simplify some steps, we have implemented &lt;em&gt;feature flipping&lt;/em&gt; on the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In our config there is a &lt;em&gt;map&lt;/em&gt; key/value that lists all the features of the application associated with their activation status.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;featureFlipping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;myAwesomeFeature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;anotherOne&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our code, we have implemented conditional treatments that say “If this feature is activated then…“. This can change the rendering of a component, change the implementation of a Redux action or disable a route in our &lt;em&gt;react-router&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what’s the point?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can develop new evolutions progressively by hiding them behind a configuration key. We deliver features in production without activating them.&lt;/li&gt;
&lt;li&gt;In a test environment, we can overload this config to test features that are not yet activated in production.&lt;/li&gt;
&lt;li&gt;In the case of a white label site, we can propose these features to our customers as possible options.&lt;/li&gt;
&lt;li&gt;Before deleting code of a feature, we deactivate it and clean it up without risk.&lt;/li&gt;
&lt;li&gt;Thanks to an in-house tool called &lt;em&gt;Applaunch&lt;/em&gt;, this feature flipping config can be overloaded on time in a GUI without deployment. This allows us to activate features without putting the code into production. In the event of an incident, we can deactivate features that have been degraded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To give you a more concrete example, between 2018 and 2020 we completely overhauled the application’s interface. This graphical evolution was just a featureFlipping key. The graphical redesign was not a reset of the project, we still live with both versions (as long as the switchover of all our customers is not completed).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rqhmue6n3rahpvv0xtc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rqhmue6n3rahpvv0xtc.jpg" alt="screenshot comparing v4 / v5 on 6play" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A/B testing
&lt;/h3&gt;

&lt;p&gt;Thanks to the great work of the backend and data teams, we were even able to extend the use of &lt;em&gt;feature flipping&lt;/em&gt; by making this configuration modifiable for sub-groups of users.&lt;/p&gt;

&lt;p&gt;This allows us to deploy new features on a smaller portion of users in order to compare our &lt;a href="https://en.wikipedia.org/wiki/Performance_indicator" rel="noopener noreferrer"&gt;KPI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Decision making, technical or product performance improvement, experimentation, the possibilities are numerous and we exploit them more and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;em&gt;future flipping&lt;/em&gt;.
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Based on an original idea by &lt;a href="https://twitter.com/SuperFlaw" rel="noopener noreferrer"&gt;Florent Lepretre&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We regularly had the need to activate features at &lt;del&gt;very&lt;/del&gt; early hours in the future. For that we had to be connected at a precise time on our computer to modify the configuration on the fly.&lt;/p&gt;

&lt;p&gt;To avoid forgetting to do this, or doing it late, we made sure that a configuration key could be activated from a certain date. To do this, we evolved our &lt;em&gt;selector redux&lt;/em&gt; which indicated if a feature was activated so that it could handle date formats and compare them to the current time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;featureFlipping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;myAwesomeFeature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;offDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2021-07-12 20:30:00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2021-07-12 19:30:00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Many coffees ☕️ at 9am have been saved by &lt;em&gt;future flipping&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Monitor, Measure, Alert
&lt;/h2&gt;

&lt;p&gt;To maintain a project as long as bedrock’s web application, testing, documentation and rigor are not enough. You also need visibility on what works in production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do you know that the application you have in production right now is working as expected?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We assume that no functionality works until it is monitored. Today, monitoring in Bedrock on the frontend side takes the form of different tools and different stacks. I could quote &lt;a href="https://newrelic.com/" rel="noopener noreferrer"&gt;NewRelic&lt;/a&gt;, a &lt;a href="https://github.com/statsd/statsd" rel="noopener noreferrer"&gt;Statsd&lt;/a&gt;, a &lt;a href="https://www.elastic.co/fr/what-is/elk-stack" rel="noopener noreferrer"&gt;ELK&lt;/a&gt; stack or even &lt;a href="https://youbora.nicepeopleatwork.com/" rel="noopener noreferrer"&gt;Youbora&lt;/a&gt; for the video.&lt;/p&gt;

&lt;p&gt;To give you an example, each time a user starts a browsing session we send an anonymous monitoring &lt;em&gt;Hit&lt;/em&gt; to increment a counter in Statsd. We then have to define a dashboard that displays the evolution of this number in a graph. If we observe a too important variation, it can allow us to detect an incident.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqewjsr2njehktz4dcxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqewjsr2njehktz4dcxu.png" alt="example of monitoring dashboard" width="799" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Monitoring also offers us solutions to understand and analyze a bug that occurred in the past. Understanding an incident, explaining it, finding its root cause are the possibilities that are open to you if you monitor your application. Monitoring can also allow you to better communicate with your customers about the impact of an incident and also to estimate the number of impacted users.&lt;/p&gt;

&lt;p&gt;With the multiplication of our customers, monitoring our platforms well is not enough. Too much data, too many dashboards to monitor, it becomes very easy to miss something. So we started to complement our metrics monitoring with automatic &lt;em&gt;alerting&lt;/em&gt;. Once we have enough confidence in the metrics, we can easily set up alerts that will warn us if there is an inconsistent value.&lt;/p&gt;

&lt;p&gt;However, we try to always trigger alerts only when it is actionable. In other words, if an alert sounds, we have something to do. Sounding alerts that do not require immediate human action generates noise and wastes time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsa03ytio1zueetbeeuxb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsa03ytio1zueetbeeuxb.gif" alt="general alert" width="498" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Limit, monitor and update your dependencies
&lt;/h2&gt;

&lt;p&gt;What goes out of date faster than your shadow in a web project based on javascript technologies are your dependencies. The ecosystem evolves rapidly and your dependencies can quickly become unmaintained, out of fashion or completely overhauled with big &lt;em&gt;breaking changes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We therefore try as much as possible to limit our dependencies and avoid adding them unnecessarily. A dependency is often very easy to add but it can become a real headache to remove.&lt;/p&gt;

&lt;p&gt;The graphic component libraries (e.g. React bootstrap, Material Design) are a good example of dependencies that we do not want to introduce. They can make integration easier at first, but they often freeze the version of your component library later on. You don’t want to freeze the React version in your application for two form components.&lt;/p&gt;

&lt;p&gt;Monitoring is also part of our dependency management routines. Since the addition of &lt;a href="https://docs.npmjs.com/auditing-package-dependencies-for-security-vulnerabilities" rel="noopener noreferrer"&gt;reporting security flaws in an NPM package&lt;/a&gt;, it is possible to know if a project has a dependency that contains a known security flaw with a simple command. So we have daily jobs on our projects that run the &lt;code&gt;yarn audit&lt;/code&gt; command to force us to apply patches.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dependency maintenance is greatly facilitated by our E2E test stack which sounds the alarm if the version upgrade generates a regression.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today, except for security flaws, we update our dependencies “when we have time”, often at the end of &lt;em&gt;sprint&lt;/em&gt;. We are not satisfied with this because some dependencies can be forgotten. I personally use tools like &lt;a href="https://classic.yarnpkg.com/en/docs/cli/outdated/" rel="noopener noreferrer"&gt;&lt;code&gt;yarn outdated&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://dependabot.com/" rel="noopener noreferrer"&gt;Dependabot&lt;/a&gt; on my personal projects to automate the update of my dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accepting your technical debt
&lt;/h2&gt;

&lt;p&gt;A project will always accumulate technical debt. &lt;strong&gt;This is a fact.&lt;/strong&gt; Whether it is voluntary or involuntary debt, a project that resists the years will inevitably accumulate debt. Even more so, if during all these years you keep adding features.&lt;/p&gt;

&lt;p&gt;Since 2014, our best practices, our ways of doing things have evolved well. Sometimes we decided these changes but sometimes we underwent them (an example, the arrival of functional components with React and the Hooks api).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our project is not completely &lt;em&gt;“state of art”&lt;/em&gt; and we assume it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdj9koo9ksvhoopaidmst.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdj9koo9ksvhoopaidmst.gif" alt="It will hold!" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We try to prioritize our &lt;em&gt;refactoring&lt;/em&gt; topics on the parts of the application on which we have the most concern, the most pain. We consider that a part of the application that we don’t like but on which we don’t need to work (bring evolutions) doesn’t deserve that we refactor it.&lt;/p&gt;

&lt;p&gt;I could name many features of our application that have not evolved functionally for several years. But since we have covered these features with E2E tests since the beginning, we didn’t really have to touch them.&lt;/p&gt;

&lt;p&gt;As said above, the next evolution of a code feature is sometimes its deactivation. So why spend time rewriting the whole application?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In any case, the code becomes “legacy”.&lt;/li&gt;
&lt;li&gt;As long as the features are tested, nothing obliges us to refactor everything permanently so that our entire codebase is &lt;em&gt;state of art&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;We focus on our &lt;em&gt;pain points&lt;/em&gt;, we re-factor what we really need to evolve.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  To summarize
&lt;/h2&gt;

&lt;p&gt;The best practices presented here are obviously subjective and will not be perfectly/directly applicable in your contexts. However, I am convinced that they can probably help you identify what can make your project go from fun to stale. At Bedrock we have other practices in place that I haven’t listed here but that will be the occasion for a new article sometime.&lt;/p&gt;

&lt;p&gt;Finally, if you want me to go into more detail on some of the chapters presented here, don’t hesitate to tell me, I could try to dedicate a specific article to it.&lt;/p&gt;

</description>
      <category>web</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to setup end to end tests with WebdriverIo on Github action ?</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Fri, 13 Aug 2021 06:34:27 +0000</pubDate>
      <link>https://dev.to/slashgear_/how-to-setup-end-to-end-tests-with-webdriverio-on-github-action-f9n</link>
      <guid>https://dev.to/slashgear_/how-to-setup-end-to-end-tests-with-webdriverio-on-github-action-f9n</guid>
      <description>&lt;p&gt;I recently set up an end-to-end testing stack to make sure I wasn’t going to introduce any functional regressions to my blog. With several years of experience using E2E (End to End) testing technologies, I still encountered some difficulties in setting it up.&lt;/p&gt;

&lt;p&gt;In order to save you time I give you a step by step tuto for the installation of the tools necessary to have an efficient test stack in a few minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it run locally first
&lt;/h2&gt;

&lt;p&gt;You need to install some dependencies to your project first. For me the project was a quite simple Gatsby site, but you could settle it on whatever website you develop that have a &lt;code&gt;package.json&lt;/code&gt; file. In this case I will start from a just initialised &lt;code&gt;npm&lt;/code&gt; package. &lt;strong&gt;Make sure you have a Chromium or Chrome browser installed locally.&lt;/strong&gt; We are really lucky because &lt;a href="https://webdriver.io/" rel="noopener noreferrer"&gt;WebdriverIo&lt;/a&gt; teams have developed a CLI topic that does this job for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; @wdio/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then all you need is to trigger the initialisation of a new configuration. The CLI will show you a prompt you should follow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn wdio config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fslashgear.github.io%2F98f39f6c4c1dabf691434adfc56a6ff9%2Fsetup.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fslashgear.github.io%2F98f39f6c4c1dabf691434adfc56a6ff9%2Fsetup.gif" alt="exemple screen captured of the CLI output" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is what you should do for each question:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select first &lt;code&gt;On my local machine&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You should definitely use &lt;code&gt;cucumber&lt;/code&gt;, Gherkin language is so great to &lt;a href="https://en.wikipedia.org/wiki/Behavior-driven_development" rel="noopener noreferrer"&gt;declare humanly readable user behaviors&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Choose the compiler you need, I personally use &lt;code&gt;No&lt;/code&gt;. You could decide if you wannt to handle Babel or Typescript depending on you habits.&lt;/li&gt;
&lt;li&gt;For the &lt;code&gt;Where are your feature files located?&lt;/code&gt; I like to use the default value.&lt;/li&gt;
&lt;li&gt;Same for step definitions, use default value.&lt;/li&gt;
&lt;li&gt;Let’s see what tests WebdriverIo will autogenerate.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Do you want to use page objects ?&lt;/code&gt; I wouldn’t use that if it is the first time you setup those kind of testing stack, say &lt;code&gt;n&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You should select &lt;code&gt;spec&lt;/code&gt; reporter here.&lt;/li&gt;
&lt;li&gt;Please select &lt;code&gt;chromedriver&lt;/code&gt; service only.&lt;/li&gt;
&lt;li&gt;Override the base url with you local context (for example, add the port on which you expose your app locally).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Normally the CLI generates some files for you and add the missing dependencies you need.&lt;/p&gt;

&lt;p&gt;Here is my &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-wdio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@wdio/cli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.9.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@wdio/cucumber-framework"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.9.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@wdio/local-runner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.9.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@wdio/spec-reporter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.9.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chromedriver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^92.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wdio-chromedriver-service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.2.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a &lt;code&gt;login.feature&lt;/code&gt; file in &lt;code&gt;./features/&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kd"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; The Internet Guinea Pig Website

  &lt;span class="kn"&gt;Scenario Outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; As a user, I can log into the secure area

    &lt;span class="nf"&gt;Given &lt;/span&gt;I am on the login page
    &lt;span class="nf"&gt;When &lt;/span&gt;I login with &lt;span class="nv"&gt;&amp;lt;username&amp;gt;&lt;/span&gt; and &lt;span class="nv"&gt;&amp;lt;password&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;Then &lt;/span&gt;I should see a flash message saying &lt;span class="nv"&gt;&amp;lt;message&amp;gt;&lt;/span&gt;

    &lt;span class="nn"&gt;Examples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;username&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tomsmith&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;SuperSecretPassword!&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;logged&lt;/span&gt; &lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;secure&lt;/span&gt; &lt;span class="n"&gt;area!&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;foobar&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;barfoo&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Your&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;invalid!&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the steps definitions in &lt;code&gt;./step-definitions/steps.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Given&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;When&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Then&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cucumber/cucumber&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nc"&gt;Given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^I am on the &lt;/span&gt;&lt;span class="se"&gt;(\w&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt; page$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://the-internet.herokuapp.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nc"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^I login with &lt;/span&gt;&lt;span class="se"&gt;(\w&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt; and &lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nc"&gt;Then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^I should see a flash message saying &lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeExisting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTextContaining&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s see what those default example tests are doing ! For that, you just have to type this in your console and tada 🎉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn wdio run wdio.conf.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fslashgear.github.io%2F91922b6797f633320658a98c56f955cf%2Ftest-run.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fslashgear.github.io%2F91922b6797f633320658a98c56f955cf%2Ftest-run.gif" alt="Here is a gif of what happened in my Chrome browser" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the log it will generate thanks to the &lt;code&gt;spec&lt;/code&gt; reporter !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;------------------------------------------------------------------
[chrome 92.0.4515.131 mac os x #0-0] Running: chrome (v92.0.4515.131) on mac os x
[chrome 92.0.4515.131 mac os x #0-0] Session ID: edd73da800a210e7c677c69cd064004f
[chrome 92.0.4515.131 mac os x #0-0]
[chrome 92.0.4515.131 mac os x #0-0] » /features/login.feature
[chrome 92.0.4515.131 mac os x #0-0] The Internet Guinea Pig Website
[chrome 92.0.4515.131 mac os x #0-0] As a user, I can log into the secure area
[chrome 92.0.4515.131 mac os x #0-0] ✓ Given I am on the login page
[chrome 92.0.4515.131 mac os x #0-0] ✓ When I login with tomsmith and SuperSecretPassword!
[chrome 92.0.4515.131 mac os x #0-0] ✓ Then I should see a flash message saying You logged into a secure area!
[chrome 92.0.4515.131 mac os x #0-0]
[chrome 92.0.4515.131 mac os x #0-0] As a user, I can log into the secure area
[chrome 92.0.4515.131 mac os x #0-0] ✓ Given I am on the login page
[chrome 92.0.4515.131 mac os x #0-0] ✓ When I login with foobar and barfoo
[chrome 92.0.4515.131 mac os x #0-0] ✓ Then I should see a flash message saying Your username is invalid!
[chrome 92.0.4515.131 mac os x #0-0]
[chrome 92.0.4515.131 mac os x #0-0] 6 passing (3s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For now the tests are not testing your application at all. You will find many ressources on how to use &lt;a href="https://cucumber.io/docs/installation/javascript/" rel="noopener noreferrer"&gt;Cucumber JS&lt;/a&gt; to write great test to describe and test your application features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure the Github action workflow
&lt;/h2&gt;

&lt;p&gt;Now that we have managed to run E2E tests on our machine, we just need to set up a continuous integration workflow that will automatically check on your Pull Request and on your &lt;code&gt;main&lt;/code&gt; branch that all the tests are ok.&lt;/p&gt;

&lt;p&gt;I use for most of my projects Github Action and I’m happy with it, so the following example will use this tool. However, the principle also works with a Gitlab, Jenkins or other pipeline.&lt;/p&gt;

&lt;p&gt;With Github Action you need to setup a Yaml file to describe you workflow. Let’s create &lt;code&gt;./.github/workflows/continuous-integration.yml&lt;/code&gt; file in your project !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Continuous Integration&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;**'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# First you need to install a chromium browser in your runner&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Chromium&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get install chromium-browser&lt;/span&gt;

      &lt;span class="c1"&gt;# You fetch the current ref&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="c1"&gt;# Use Node version above 14.x you want to use&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16.x&lt;/span&gt;

      &lt;span class="c1"&gt;# Install your dependencies (with yarn, npm no matter)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn install&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;

      &lt;span class="c1"&gt;# This is where you could build your app&lt;/span&gt;
      &lt;span class="c1"&gt;# You could also start your server process (take a look at https://github.com/Slashgear/slashgear.github.io/blob/source/.github/workflows/continuous-integration.yml)&lt;/span&gt;

      &lt;span class="c1"&gt;# Run your test with the same command you uses locally&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn wdio run wdio.conf.js&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Running E2E tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s try that ! 🚀&lt;/p&gt;

&lt;p&gt;😭 Sadly you should face a very common error with E2E testing with chrome.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0-0] 2021-08-12T20:34:12.293Z ERROR webdriver: Request failed with status 500 due to unknown error: unknown error: Chrome failed to start: exited abnormally.
[0-0] (unknown error: DevToolsActivePort file doesn't exist)
[0-0] (The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.)
[0-0] 2021-08-12T20:34:12.293Z ERROR webdriver: #0 0x5631c488ba63 &amp;lt;unknown&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DevToolsActivePort file doesn't exist&lt;/code&gt; basically means your browser did not succeed to start. The main reason of the issue is the fact that you ask WebdriverIo to start Chrome browser with a Graphic User Interface on a ubuntu runner that don’t have a screen at all 😅.&lt;/p&gt;

&lt;p&gt;We need to create a new configuration of Webdriver specific to github action that extends our basic one. Let’s create &lt;code&gt;wdio-github.conf.js&lt;/code&gt; next to &lt;code&gt;wdio.conf.js&lt;/code&gt; !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./wdio.conf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// We only need to override the Chrome configuration of capabilities&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;maxInstances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;browserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;acceptInsecureCerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// We need to extends some Chrome flags in order to tell Chrome to run headless&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;goog:chromeOptions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--headless&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--disable-gpu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--disable-dev-shm-usage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now just have to change the &lt;code&gt;yarn wdio run&lt;/code&gt; command in our Github Workflow YAML file. You just need to push it and github will start it for you !&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1x2qcpwo0kw4ld7a86b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1x2qcpwo0kw4ld7a86b.png" alt="Yeah" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup multi browser configuration !
&lt;/h2&gt;

&lt;p&gt;Chrome is not the only browser, and I hope it never will never be !&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebdriverIo is a great solution to run test on multiple Browsers.&lt;/strong&gt; And I won’t show you here what great features you could use with remote running solution like &lt;a href="https://www.browserstack.com/" rel="noopener noreferrer"&gt;BrowserStack&lt;/a&gt; or &lt;a href="https://saucelabs.com/" rel="noopener noreferrer"&gt;Saucelabs&lt;/a&gt; directly with WebdriverIo.&lt;/p&gt;

&lt;p&gt;Let’s configure a Firefox locally !&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make sur to install a Java JDK 8 on your machine. Small trick for Macos users like me run &lt;code&gt;brew install adoptopenjdk/openjdk/adoptopenjdk8&lt;/code&gt; does the job !&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;yarn add -D @wdio/selenium-standalone-service&lt;/code&gt; to install selenium services working with WebdriverIo.&lt;/li&gt;
&lt;li&gt;Make sure you have Firefox installed locally.&lt;/li&gt;
&lt;li&gt;Now the last thing we need to do is to update our configuration to add firefox capabilities.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;code&gt;wdio.conf.js&lt;/code&gt;, just replace &lt;em&gt;capabilities&lt;/em&gt; and &lt;em&gt;services&lt;/em&gt; arrays with this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;capabilites&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;maxInstances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;browserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;acceptInsecureCerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;maxInstances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;browserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;acceptInsecureCerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chromedriver&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;selenium-standalone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run the command &lt;code&gt;yarn wdio run wdio.conf.js&lt;/code&gt; now, it will trigger both test on Firefox and Chrome and that is completely awesome !&lt;/p&gt;

&lt;p&gt;The last thing we need to do is to update our Github specific configuration in order to make it work also in your continuous integration. You need to update &lt;em&gt;capabilites&lt;/em&gt; too, in order to add Firefox and to make it boot &lt;em&gt;Headless&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;wdio-github.conf.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./wdio.conf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// We only need to override the Chrome configuration of capabilities&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;maxInstances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;browserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;acceptInsecureCerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// We need to extends some Chrome flags in order to tell Chrome to run headless&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;goog:chromeOptions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--headless&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--disable-gpu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--disable-dev-shm-usage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;maxInstances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;browserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;acceptInsecureCerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;moz:firefoxOptions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-headless&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;And that’s all folks !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Have fun covering your application with E2E tests ! Don’t hesitate to @ me on twitter if this &lt;em&gt;How to&lt;/em&gt; helped you.&lt;/p&gt;

&lt;p&gt;If you need to see the example application I used in this tutorial, take a look at &lt;a href="https://github.com/Slashgear/example-wdio" rel="noopener noreferrer"&gt;this example Github repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>bdd</category>
    </item>
    <item>
      <title>Should we replace webpack by 🗻 Snowpack 🗻?️</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Thu, 03 Jun 2021 21:27:34 +0000</pubDate>
      <link>https://dev.to/slashgear_/should-we-replace-webpack-by-snowpack-11de</link>
      <guid>https://dev.to/slashgear_/should-we-replace-webpack-by-snowpack-11de</guid>
      <description>&lt;p&gt;After webpack, esbuild and vitejs, let’s take a look at snowpack. The latter seems to be making a name for itself in the bundler ecosystem for a few years now. So I suggest that we study its functionalities, its strong points but also its weak points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snowpack
&lt;/h2&gt;

&lt;p&gt;What is Snowpack? It is a web application packager that offers :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a rich and powerful development experience&lt;/li&gt;
&lt;li&gt;a &lt;em&gt;production&lt;/em&gt; mode that offers the features necessary to optimize the assets of the site.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# npm:&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; snowpack
&lt;span class="c"&gt;# yarn:&lt;/span&gt;
yarn add &lt;span class="nt"&gt;--dev&lt;/span&gt; snowpack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ESModule in the browser
&lt;/h3&gt;

&lt;p&gt;Packagers such as webpack and rollup rely on the construction of a dependency tree that is analyzed and packaged at each modification. The build step of the modified files is still necessary, but the packaging operation (merging the different modules in the form of a bundle that is sent to the browser) is no longer necessary. Our browsers now know how to manage ESModules](&lt;a href="https://caniuse.com/?search=esmodule" rel="noopener noreferrer"&gt;https://caniuse.com/?search=esmodule&lt;/a&gt;), so it is no longer necessary to package our source modules to merge them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is the same idea that Evan You has taken up in &lt;a href="https://slashgear.github.io//vite-webpack-killer" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this principle, the modification of a file triggers only the build of this file, and this file only. You can have a project with thousands of javascript modules, the build time for each modification will not be affected. For dependencies (&lt;em&gt;vendors&lt;/em&gt;), Snowpack builds them once and caches them to do it again only if they have changed.&lt;/p&gt;

&lt;p&gt;Here is a diagram in the Snowpack doc showing the interest to avoid packaging in dev.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbew2a8ox5mml1wa6irb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbew2a8ox5mml1wa6irb.png" alt="explanation of the way snowpack works, which is based on the build only" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use a &lt;em&gt;Create React App&lt;/em&gt; project, you just have to add Snowpack as a dependency, you don’t have to change anything if you didn’t extend the webpack config. I invite you to test snowpack with the template generators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-snowpack-app react-snowpack &lt;span class="nt"&gt;--template&lt;/span&gt; @snowpack/app-template-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t worry! Snowpack is not only compatible with React, you can use Vue, Svelte or just javascript.&lt;/p&gt;

&lt;h3&gt;
  
  
  A rich configuration (maybe too much)
&lt;/h3&gt;

&lt;p&gt;Like webpack, snowpack proposes to configure its use by an object. I must admit that &lt;a href="https://dev.to/vitejs-concurrent-performant-webpack-for-react/"&gt;after playing with it quickly&lt;/a&gt;, I am a bit disappointed. I see a meager &lt;a href="https://www.snowpack.dev/reference/configuration" rel="noopener noreferrer"&gt;documentation page&lt;/a&gt; that seems to describe many parameterizable keys.&lt;/p&gt;

&lt;p&gt;If you come from webpack, you won’t be lost, it’s very similar without being exactly the same.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not touching the default configuration can be an excellent idea.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  A nice collection of plugins
&lt;/h3&gt;

&lt;p&gt;Snowpack is not that new. A community has built up to create &lt;a href="https://www.snowpack.dev/plugins" rel="noopener noreferrer"&gt;a rich plugin ecosystem&lt;/a&gt;. Some of these plugins seem “core” because they are under the &lt;em&gt;@snowpack&lt;/em&gt; scope but many packages are ported by a few people independent of the project. It’s reassuring without being reassuring, I’ve personally experienced webpack updates being blocked/delayed while waiting for compatibility of some plugins that were no longer maintained.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjpo90at45n7mx66onb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjpo90at45n7mx66onb3.png" alt="plugins list page screenshot" width="800" height="1135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Beware of the plugins you use!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Contrary to Vite which natively offers a lot of nice features, snowpack works like webpack by relying on plugins to enrich the API. It’s a gamble, it can be complicated to keep this ecosystem of plugins up to date and efficient to continue to guarantee the interest of a migration to snowpack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Side Rendering
&lt;/h3&gt;

&lt;p&gt;Snowpack offers a solution to implement your applications with server-side rendering. It is clear that the need for SSR is still felt on our frontend applications for SEO or rendering performance reasons. Unfortunately, on the application packaging side, it is still complicated and it is often necessary to make two builds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a build for the client aka the Browser&lt;/li&gt;
&lt;li&gt;a build for the Node server&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Double build, double punishment!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://www.snowpack.dev/guides/server-side-render#option-3%3A-server-side-rendering-(ssr)" rel="noopener noreferrer"&gt;technique proposed by Snowpack&lt;/a&gt; remains limited but it’s still correct. I propose a slight improvement of the implementation by adding the buffered server rendering mechanics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;startServer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snowpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;startServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getServerRuntime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;importedComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dist/MyReactComponent.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyReactComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;importedComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ReactDOMServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;renderToNodeStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MyReactComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="c1"&gt;// Directly write the head of page&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
      &amp;lt;title&amp;gt;Hello 👋&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
    &amp;lt;div id="app"&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// Render bufferized version of App&lt;/span&gt;
  &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// When buffer end, we add the closing tags of page&lt;/span&gt;
  &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;/div&amp;gt;
      &amp;lt;/body&amp;gt;
      &amp;lt;/html&amp;gt;
    `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Webpack, esbuild, quick, or snowpack ?
&lt;/h2&gt;

&lt;p&gt;Clearly after this series of articles where I tried to study this new generation of tools to bundle applications, I must admit that I am very surprised. We can clearly see that the support of ES Modules in the browser marks the entry into a new era. As Sindre Sorhus reminds us in &lt;a href="https://blog.sindresorhus.com/hello-modules-d1010b4e777b" rel="noopener noreferrer"&gt;his latest article&lt;/a&gt;, with the end of Node 10 support and the capabilities of our current browsers, it is no longer necessary to target CJS.&lt;/p&gt;

&lt;p&gt;Caching strategies and the use of CJS modules seem to be outdated for our needs in development environments. We can see that Vite and Snowpack offer this new mechanism which seems to be really efficient. Doing a &lt;em&gt;once for all&lt;/em&gt; build of the libraries and of each source file is a great idea to not suffer from a too slow startup time of our big web applications.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep an eye on Esbuild&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, the performance of these new tools also relies heavily on Esbuild. The idea of using a more optimized stack to read, parse, combine JS or TS modules with languages that allow for finer IO and memory management is really the keystone of this new generation of tools. Even before choosing whether to stay with webpack, or use Vite and Snowpack, it is certain that Esbuild will have to be followed closely. This lib has not finished surprising us. You should also look at a tool like &lt;a href="https://swc.rs/" rel="noopener noreferrer"&gt;SWC&lt;/a&gt; which is a direct competitor of Esbuild. There are certainly other tools being created at the moment.&lt;/p&gt;

&lt;h3&gt;
  
  
  I use webpack and have configured it a lot
&lt;/h3&gt;

&lt;p&gt;If you are in this situation, you may unfortunately be forced to keep webpack. This is not bad news, it is a very good tool that is far from being obsolete. It is likely that the webpack team will come up with new performance improvements, perhaps through the use of ESModules.&lt;/p&gt;

&lt;p&gt;You can also try to use snowpack in a development environment. There is &lt;a href="https://www.npmjs.com/package/@snowpack/plugin-webpack" rel="noopener noreferrer"&gt;a plugin to use webpack in the snowpack prod build&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  I really want to reduce the configuration of my application build
&lt;/h3&gt;

&lt;p&gt;If you don’t want to keep your webpack configuration files anymore, which can be sometimes difficult to maintain, the alternative proposed by Vite can be a great option. Just keep in mind that this solution is still young.&lt;/p&gt;

&lt;p&gt;If you are going to use Vite, I suggest that you minimize the configuration you can do to it. This will make it easier for you to keep up with the new versions that are likely to arrive in the coming months.&lt;/p&gt;

&lt;h3&gt;
  
  
  I use a CLI that manages my build configuration for me
&lt;/h3&gt;

&lt;p&gt;You are using VueCLI, CRA, or other and you have not ejected your configuration. You don’t like to touch the build configuration of your application because the tools are complex and you don’t want to spend a lot of time configuring them. So I recommend you to stay as much as possible with the default configuration of your project as long as its performance does not bother you.&lt;/p&gt;

&lt;p&gt;However, nothing prevents you from testing tools like Vite that work directly without configuration with your already generated projects. If the startup time of your development environment becomes too important, this can really be an interesting solution for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  And it’s already the end?
&lt;/h2&gt;

&lt;p&gt;I think I’ve covered the new tools proposed by the community to package our web applications. If you have other tools that would be interesting to look at, don’t hesitate to suggest them to me on a social network like Twitter. See you soon for new &lt;em&gt;javascript peregrinations&lt;/em&gt; !👋&lt;/p&gt;

&lt;p&gt;Thanks to Jérémie for the review 🤗&lt;/p&gt;

</description>
      <category>webpack</category>
      <category>snowpack</category>
      <category>vitejs</category>
      <category>bundler</category>
    </item>
    <item>
      <title>Vite, a new webpack killer ?!😮</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Fri, 26 Mar 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/slashgear_/vite-a-new-webpack-killer-162g</link>
      <guid>https://dev.to/slashgear_/vite-a-new-webpack-killer-162g</guid>
      <description>&lt;p&gt;After having dealt on this blog &lt;a href="https://dev.to/en/webpack/"&gt;in several articles about webpack&lt;/a&gt;, I have started a new series of articles about new generation bundlers. It seems that we are now entering a new era of web tooling.&lt;/p&gt;

&lt;p&gt;Having introduced &lt;a href="//../esbuild-bundler-incredibly-fast-and-promising"&gt;esbuild and its interesting features&lt;/a&gt;, it seems logical to me to deal with the &lt;em&gt;“little brothers”&lt;/em&gt; bundlers. So let’s start with Vite, a project from the VueJS community and started by &lt;a href="https://github.com/yyx990803" rel="noopener noreferrer"&gt;Evan You&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same starting point
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;Vite&lt;/code&gt; we are in the same situation as with &lt;code&gt;esbuild&lt;/code&gt;. Globally the community is happy with the current tools even if they have some performance issues.&lt;/p&gt;

&lt;p&gt;As I said in my previous article, having a big JS project today often means having a dev and prod environment that is sometimes a bit slow. Between the choice of tools that do not sufficiently exploit parallelization or memory optimization, or repetitive operations that exploit caching very little, it is easy to identify culprits for these slowness.&lt;/p&gt;

&lt;p&gt;NB: &lt;em&gt;I have proposed some solutions to &lt;a href="https://dev.to/hunting-webpack-performances/"&gt;improve the performance of your webpack build in this article&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Moreover, the &lt;em&gt;second generation&lt;/em&gt; tools (like webpack, Rollup, Parcel) could not handle from the beginning &lt;a href="https://caniuse.com/?search=es6" rel="noopener noreferrer"&gt;recent features of our browsers like ESmodules&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the idea?
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;revolutionary&lt;/em&gt; idea of &lt;code&gt;Vite&lt;/code&gt; is to combine two tools for two different needs in order to optimize the build to be as fast as possible. The two tools that make up &lt;code&gt;Vite&lt;/code&gt; are &lt;code&gt;esbuild&lt;/code&gt; and &lt;code&gt;Rollup&lt;/code&gt;, so nothing new. But why two bundling tools? Basically, for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our dependencies don’t change often, so re-evaluating their module tree at each build is not necessary. We can generate the bundle of our &lt;em&gt;vendor&lt;/em&gt; once and for all with an optimized tool like &lt;code&gt;esbuild&lt;/code&gt;. This bundler is very fast and allows a quick start of the server.&lt;/li&gt;
&lt;li&gt;The modules in our source code are subject to a lot of changes unlike the dependencies. So &lt;code&gt;Vite&lt;/code&gt; uses another treatment based on ESM that works natively on recent browsers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to read &lt;a href="https://vitejs.dev/guide/why.html" rel="noopener noreferrer"&gt;this doc page&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  In fact, how does it work?
&lt;/h2&gt;

&lt;p&gt;In order to play a little with the tool, I propose a small presentation through an example project. Let’s start by creating an example project. I show you here how to create it and put you &lt;a href="https://github.com/Slashgear/example-vite" rel="noopener noreferrer"&gt;the link of the github repository in which I published it&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="s2"&gt;"example-vite"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"example-vite"&lt;/span&gt;

git init
yarn init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Installing &lt;code&gt;Vite&lt;/code&gt; is very easy, you just need a dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the purpose of this example, I propose an example with React (there are already many examples with Vue 😉 )&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add react react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s add 3 usual commands to launch &lt;code&gt;Vite&lt;/code&gt; in the &lt;code&gt;package.json&lt;/code&gt; file&lt;/p&gt;

&lt;p&gt;&lt;em&gt;package.json&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite preview"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we need some small source files for &lt;code&gt;Vite&lt;/code&gt; to play with.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;index.html&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Example Application with Vite&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/src/main.jsx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;src/main.jsx&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello world dear readers ! &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you just have to start the development server with this command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 Normally, within milliseconds &lt;code&gt;Vite&lt;/code&gt; has started a server and if you go to &lt;code&gt;https://localhost:3000&lt;/code&gt; that presents you with this beautiful application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t6vk9jun9ntjkatvd4i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t6vk9jun9ntjkatvd4i.png" alt="screenshot step 1" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s not lie, it’s a bit sad, let’s see how &lt;code&gt;Vite&lt;/code&gt; does if we add some CSS. Let’s put a beautiful color &lt;a href="https://fr.wikipedia.org/wiki/Chartreuse_(liqueur)" rel="noopener noreferrer"&gt;chartreuse&lt;/a&gt; to this title. Let’s add the following stylesheet.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;src/index.css&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;chartreuse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we just need to add an import to this new file.&lt;em&gt;src/main.jsx&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./index.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There, now you have a beautiful color that &lt;a href="https://webaim.org/articles/contrast/" rel="noopener noreferrer"&gt;clearly lacks contrast to be accessible&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj90horlhgx64ak2hbr8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj90horlhgx64ak2hbr8i.png" alt="screenshot with colors" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you now try to run the &lt;code&gt;yarn build&lt;/code&gt; command, you can see that &lt;code&gt;Vite&lt;/code&gt; will build you a &lt;code&gt;dist&lt;/code&gt; folder. Without any settings I have these different resources ready to be deployed on a static server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmilbnyr0jmstsoredwcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmilbnyr0jmstsoredwcf.png" alt="dist directory screenshot" width="506" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can observe that natively &lt;code&gt;Vite&lt;/code&gt; exports 2 javascript bundles (1 for the sources, 1 for the &lt;em&gt;vendors/dependencies&lt;/em&gt;) and a CSS bundle that exports the style that has been imported in your application. And this is clearly a big plus of &lt;code&gt;Vite&lt;/code&gt; compared to the competition of other tools (although parcel offers some of the same logic). The &lt;em&gt;build&lt;/em&gt; is extremely fast and does what you would expect it to do without having to configure it. Sorry but I think it’s great!&lt;/p&gt;

&lt;p&gt;I don’t know if you know &lt;a href="https://www.npmjs.com/package/react-refresh" rel="noopener noreferrer"&gt;&lt;code&gt;react-refresh&lt;/code&gt;&lt;/a&gt;, the official React package that allows you to optimize the auto-refresh of a React application. This package allows you to update your React components on the fly without them losing their &lt;em&gt;state&lt;/em&gt;.&lt;code&gt;Vite&lt;/code&gt; even though it was born out of the VueJS community, is not specifically oriented towards a frontend framework. Tools like &lt;code&gt;react-refresh&lt;/code&gt; are therefore not included by default. So you have to define it in the configuration. Unfortunately, &lt;code&gt;Vite&lt;/code&gt; doesn’t fare any better than the other tools; we are forced to define yet another config file at the root of the project.&lt;/p&gt;

&lt;p&gt;So let’s install the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; @vitejs/plugin-react-refresh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;vite.config.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;reactRefresh&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react-refresh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;reactRefresh&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I wanted to test some of the more advanced features that you can expect from a quality bundler. So I set up a single page application that uses &lt;em&gt;lazy loading&lt;/em&gt;. I won’t show you how I did it in this article, it would be too long but you can go directly to &lt;a href="https://example-vite.netlify.app/" rel="noopener noreferrer"&gt;test it in your browser&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So clearly for &lt;em&gt;lazy loading&lt;/em&gt; it’s easy with &lt;code&gt;Vite&lt;/code&gt;, I’m amazed! The tool immediately detects my use of the dynamic import &lt;code&gt;import()&lt;/code&gt; to generate a separate chunk for the JS but also the CSS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lazy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F687qa7wmezlws02dljod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F687qa7wmezlws02dljod.png" alt="dist directory screenshot with lazy loading" width="570" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The strengths of Vite
&lt;/h2&gt;

&lt;p&gt;It is clear that &lt;code&gt;Vite&lt;/code&gt; has many nice features and advantages. Besides its incredible speed, I would like to note that this bundler offers a really well thought out &lt;em&gt;autoconfiguration&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the demo I gave you earlier, I didn’t show you that &lt;code&gt;Vite&lt;/code&gt; handles natively and without configuration &lt;em&gt;static files, Web Workers, WASM binaries&lt;/em&gt;. But it doesn’t stop there, we have to admit that this &lt;em&gt;magical&lt;/em&gt; tool also natively supports &lt;strong&gt;JSX and Typescript&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When it comes to style management, &lt;code&gt;Vite&lt;/code&gt; is not to be outdone. Without any plugin or configuration, it allows you to manage CSS &lt;code&gt;@imports&lt;/code&gt;, preprocessors like &lt;strong&gt;SASS and LESS, CSS modules&lt;/strong&gt; and even the postprocessor &lt;em&gt;PostCSS&lt;/em&gt; (if you define a configuration).&lt;/p&gt;

&lt;p&gt;More anecdotally, &lt;code&gt;Vite&lt;/code&gt; knows how to manage your &lt;code&gt;.env&lt;/code&gt; file to manage your environment variables thanks to &lt;a href="https://github.com/motdotla/dotenv" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But the feature that completely blew me away was the rather simple setup of the &lt;a href="https://tech.bedrockstreaming.com/spa-mode-isomorphism-js/" rel="noopener noreferrer"&gt;SSR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fslashgear.github.io%2F0e9e962814ce9f4e10f1810913a40893%2Fsurprise.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fslashgear.github.io%2F0e9e962814ce9f4e10f1810913a40893%2Fsurprise.gif" alt="surprise" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the first time I’m talking about a &lt;em&gt;bundler&lt;/em&gt; that natively handles &lt;strong&gt;Server Side Rendering&lt;/strong&gt;. I use other tools in production for the different applications I develop. Unfortunately, it is still very complicated to set up such an architecture (even with tools like Webpack).&lt;/p&gt;

&lt;p&gt;So we can see that developers are mainly turning to turnkey solutions like Next and Nuxt to manage these issues for them. This is not a bad thing in itself. However, I think that it is sometimes necessary in some projects to take control of this functionality for business needs. So we can only be happy that tools like &lt;code&gt;Vite&lt;/code&gt; have thought about it. I invite you to go &lt;a href="https://vitejs.dev/guide/ssr.html#ssr-externals" rel="noopener noreferrer"&gt;read this page of the documentation of &lt;code&gt;Vite&lt;/code&gt;&lt;/a&gt; to understand how to implement this.&lt;/p&gt;

&lt;h2&gt;
  
  
  So we stop using webpack?
&lt;/h2&gt;

&lt;p&gt;After this laudatory presentation of this tool, one could ask the question yes. However, you should not forget a simple rule.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everything that a tool tends to do magically for you often becomes much more complicated to customize.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The many choices &lt;code&gt;Vite&lt;/code&gt; seems to make to improve the &lt;em&gt;developer experience&lt;/em&gt; worry me a bit. I’m a bit afraid that all this default configuration will be complicated to maintain by the &lt;code&gt;Vite&lt;/code&gt; teams.&lt;/p&gt;

&lt;p&gt;Contrary to &lt;code&gt;esbuild&lt;/code&gt; which has the motto &lt;em&gt;“I want to offer a tool that does few things but does them very well”&lt;/em&gt;, we have here a tool that makes a lot of promises. However, we must recognize that &lt;code&gt;Vite&lt;/code&gt; also offers to use and define plugins to extend its functionalities without making them native to the main tool.&lt;/p&gt;

&lt;p&gt;Moreover, it should not be forgotten that &lt;code&gt;Vite&lt;/code&gt; is also based on &lt;code&gt;Rollup&lt;/code&gt;, a second generation bundler that benefits from a rich ecosystem of plugins that are mostly compatible. But the Rollup configuration is very complicated to edit and maintain, so I hope you won’t have to touch it if you are tempted to test &lt;code&gt;Vite&lt;/code&gt; on your applications.&lt;/p&gt;

&lt;p&gt;I would like to point out that some tools like VuePress offer today an alternative &lt;a href="https://vitepress.vuejs.org/" rel="noopener noreferrer"&gt;Vitepress&lt;/a&gt; which uses &lt;code&gt;Vite&lt;/code&gt; as a bundler.&lt;/p&gt;

&lt;p&gt;Before jumping on the &lt;code&gt;Vite&lt;/code&gt; solution, I suggest you to test another third generation bundler which is much talked about: &lt;a href="https://www.snowpack.dev/" rel="noopener noreferrer"&gt;Snowpack&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stay tuned!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>vue</category>
      <category>react</category>
      <category>webpack</category>
      <category>vite</category>
    </item>
    <item>
      <title>Esbuild, the incredibly fast 💨 and promising bundler 📈!</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Sat, 13 Mar 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/slashgear_/esbuild-the-incredibly-fast-and-promising-bundler-pbh</link>
      <guid>https://dev.to/slashgear_/esbuild-the-incredibly-fast-and-promising-bundler-pbh</guid>
      <description>&lt;p&gt;I’ve been playing with JS bundlers for several years. Still convinced of the necessity of using these tools (don’t let me believe that you don’t package your JS modules in production 😅), I played a lot with &lt;a href="https://dev.to/en/webpack/"&gt;webpack&lt;/a&gt;. Especially for performance, optimization and custom plugins usage issues.&lt;/p&gt;

&lt;p&gt;I still think that in 2021, webpack is the most industrial and successful solution to &lt;em&gt;bundle&lt;/em&gt; my web applications. I hear that tools like &lt;em&gt;parcel&lt;/em&gt; and &lt;em&gt;rollup&lt;/em&gt; are still good alternatives. However, webpack probably has the biggest community and is used by many projects.&lt;/p&gt;

&lt;p&gt;But let’s face it, today we are satisfied with these &lt;em&gt;bundling&lt;/em&gt; tools despite their poor performance. I work every day on a project with several thousands of “modules” solved by webpack and it is sometimes a pain 🥱.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Despite an intensive use of cache and workers, webpack shows some limitations to package large applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why does esbuild look interesting ?
&lt;/h2&gt;

&lt;p&gt;I can’t think of an easier way to express it than to explain it to you simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The first time I ran &lt;code&gt;esbuild&lt;/code&gt; on my test web app, I thought it crashed when in fact it ran at an absolutely insane speed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To install it, it’s not complicated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; esbuild

npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; esbuild
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or even with &lt;a href="https://www.npmjs.com/package/npx" rel="noopener noreferrer"&gt;NPX&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx esbuild &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Being written in Go, a WASM version and binaries for the main architectures are available.&lt;code&gt;esbuild&lt;/code&gt; bets on native Go to take advantage of a maximum of parallelization solutions and a better memory management.&lt;/p&gt;

&lt;h3&gt;
  
  
  A lean API by design
&lt;/h3&gt;

&lt;p&gt;Globally the API of &lt;code&gt;esbuild&lt;/code&gt; is really simple, in 30 minutes you have read all the docs of possible settings. This is far from the 3-4 hours needed to read the whole documentation of a webpack for example. In spite of a configuration that might seem limited, I am still pleasantly surprised. I have the impression that we are really close to having the &lt;em&gt;“right grammar”&lt;/em&gt; that we need to do bundling.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;esbuild&lt;/code&gt; offers 3 consumption modes:&lt;/p&gt;

&lt;h4&gt;
  
  
  CLI
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;esbuild app.jsx &lt;span class="nt"&gt;--bundle&lt;/span&gt; &lt;span class="nt"&gt;--minify&lt;/span&gt; &lt;span class="nt"&gt;--sourcemap&lt;/span&gt; &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;chrome58,firefox57,safari11,edge16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  GO
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/evanw/esbuild/pkg/api"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"os"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EntryPoints&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"app.jsx"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MinifyWhitespace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MinifyIdentifiers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MinifySyntax&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Engines&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EngineChrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"58"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EngineFirefox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"57"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EngineSafari&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"11"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EngineEdge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"16"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  JS
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;buildSync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;minify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourcemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome58&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firefox57&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;safari11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge16&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my opinion, the CLI is still very practical for testing things, but in a more “industrial” use, we still prefer the JS or GO format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugin mechanics
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/evanw" rel="noopener noreferrer"&gt;Evan Wallace&lt;/a&gt; the creator and core maintainer of &lt;code&gt;esbuild&lt;/code&gt; makes no secret of the fact that he doesn’t want his tool to meet 100% of the needs that one can have in the web world. However, this doesn’t mean that we can’t use this tool in specific cases.&lt;/p&gt;

&lt;p&gt;As we can see with other bundlers, &lt;code&gt;esbuild&lt;/code&gt; offers the mechanics of plugins that allow you to do many things. To avoid maintaining all these specific needs, the creator relies on the community to create all the plugins you could want. And clearly, the community is there, I let you see &lt;a href="https://github.com/esbuild/community-plugins" rel="noopener noreferrer"&gt;this page that lists some plugins&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The most interesting features
&lt;/h3&gt;

&lt;p&gt;I’m not going to list here the features that seem to me the heart of a Web bundler like code splitting, injection, minification. However, I was surprised by some features that are not found elsewhere.&lt;/p&gt;

&lt;h4&gt;
  
  
  An easy to understand architecture
&lt;/h4&gt;

&lt;p&gt;Clearly, what makes the strength of &lt;code&gt;esbuild&lt;/code&gt; compared to its competitors is its architecture which can be summarized simply. It is easy to understand that by combining the parallelization of the build steps and the reduction of the number of readings of the AST. I invite you to read &lt;a href="https://esbuild.github.io/faq/#why-is-esbuild-fast" rel="noopener noreferrer"&gt;more explanations in the doc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yh4azfjo27eyasr46d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yh4azfjo27eyasr46d6.png" alt="esbuild architecture diagram" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Browser targets
&lt;/h4&gt;

&lt;p&gt;By default &lt;code&gt;esbuild&lt;/code&gt; allows you to define the target of &lt;em&gt;your&lt;/em&gt; build. What &lt;em&gt;level&lt;/em&gt; of javascript do you want to achieve?&lt;/p&gt;

&lt;p&gt;Usually we use a suite of tools like &lt;code&gt;@babel/preset-env&lt;/code&gt; and a &lt;code&gt;browserlist&lt;/code&gt; to make sure we generate the JS compatible with our targeting. Babel is great, I use it every day, but piling up different tools for &lt;em&gt;bundling&lt;/em&gt; is clearly not a good solution in my eyes. It adds a lot of complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instead of learning to use a simple bundler tool, I have to learn a targeted transpilation tool on top of that&lt;/li&gt;
&lt;li&gt;I have to maintain two dependencies&lt;/li&gt;
&lt;li&gt;going through a third party librairy can reduce performances (this is a bit the bet of &lt;code&gt;esbuild&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The server mode
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;esbuild&lt;/code&gt; is so fast that it can afford to expose you an HTTP server on a folder that contains the result of your compilation on each request. Other tools usually rely on a &lt;em&gt;watch&lt;/em&gt; mode that watches for files that change to start a build.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;watch&lt;/em&gt; mode also exists with &lt;code&gt;esbuild&lt;/code&gt;, but the &lt;code&gt;serve&lt;/code&gt; mode seems to me even nicer because you just have to refresh your browser to have the latest version of your application locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;servedir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;outdir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www/js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Call "stop" on the web server when you're done&lt;/span&gt;
    &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  But then we stop everything and go on it?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Let’s save time, the answer for me is clearly no.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As the creator says in the &lt;a href="https://esbuild.github.io/faq/#production-readiness" rel="noopener noreferrer"&gt;FAQ of the doc&lt;/a&gt; in all honesty, the project is not to be considered as being in alpha. However, the tool itself does not yet have all the features that would make it a good replacement for the previous generation bundlers. I’m thinking in particular of the absence of native HMR, or of a perfectible splitting code.&lt;/p&gt;

&lt;p&gt;However, one should not remain closed on this question. Clearly &lt;code&gt;esbuild&lt;/code&gt; has very strong points that are missing in the current ecosystem. The community, still in its infancy, is rather active and the exchanges in the Issues and PR of the repo are very interesting.&lt;/p&gt;

&lt;p&gt;What I really appreciate in this project are the parts taken: a focus on performance, an API that remains simple. Finally, for once a bundler doesn’t have 1000 dependencies and add 100Mb in my &lt;code&gt;node_modules&lt;/code&gt; folder, it’s nice enough to note.&lt;/p&gt;

&lt;p&gt;I’ll finish by saying that &lt;code&gt;esbuild&lt;/code&gt; is not the only alternative that is offered to us in this new generation of &lt;em&gt;bundler&lt;/em&gt;. I intend to do this kind of analysis on tools like &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; or &lt;a href="https://www.snowpack.dev/" rel="noopener noreferrer"&gt;Snowpack&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webpack</category>
      <category>esbuild</category>
    </item>
    <item>
      <title>🎄 Advent of Code 2020 🎄: my solutions with JS</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Fri, 04 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/slashgear_/advent-of-code-2020-my-solutions-with-js-4na8</link>
      <guid>https://dev.to/slashgear_/advent-of-code-2020-my-solutions-with-js-4na8</guid>
      <description>&lt;p&gt;I decided this year to try the adventofcode2020 challenge in JS to see. I published the solutions I found in a public repository on Github. Not sure I will find solution for the upcoming challenges. 😅&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Slashgear" rel="noopener noreferrer"&gt;
        Slashgear
      &lt;/a&gt; / &lt;a href="https://github.com/Slashgear/advent-of-code" rel="noopener noreferrer"&gt;
        advent-of-code
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Personal implementation in JS of https://adventofcode.com/ challenge, don't hesitate to take a look if your are stuck 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;advent-of-code by &lt;a href="https://github.com/Slashgear" rel="noopener noreferrer"&gt;Slashgear&lt;/a&gt;
&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;In this repository you can find all the &lt;a href="https://adventofcode.com/" rel="nofollow noopener noreferrer"&gt;advent-of-code&lt;/a&gt; challenges that I passed.
All solutions are in JS only (You can run them with Node).
Feel free to look at my solutions for inspiration.&lt;/p&gt;
&lt;p&gt;Solutions are sorted by year and by day in the packages folder.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;packages/
  2019/
    day-1/
      ..
    day-2/
      ..
  2020/
     day-1/
      ..
    day-2/
      ..
    day-3/
      ..
  ..
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Slashgear/advent-of-code" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Project is a &lt;em&gt;lerna monorepo structure&lt;/em&gt; showing solution by years and days.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/
  2019/
    day-1/
      ..
    day-2/
      ..
  2020/
     day-1/
      ..
    day-2/
      ..
    day-3/
      ..
    day-4/
      ..
  ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For now, you can find solution for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Slashgear/advent-of-code/tree/main/packages/2020/day-1" rel="noopener noreferrer"&gt;2020 day 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Slashgear/advent-of-code/tree/main/packages/2020/day-2" rel="noopener noreferrer"&gt;2020 day 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Slashgear/advent-of-code/tree/main/packages/2020/day-3" rel="noopener noreferrer"&gt;2020 day 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Slashgear/advent-of-code/tree/main/packages/2020/day-4" rel="noopener noreferrer"&gt;2020 day 4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>adventofcode</category>
    </item>
  </channel>
</rss>
