<?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>Forem: Antoine Caron</title>
    <description>The latest articles on Forem by Antoine Caron (@slashgear_).</description>
    <link>https://forem.com/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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F149225%2F784d84fd-4648-416c-9a2c-b1863435f041.jpg</url>
      <title>Forem: Antoine Caron</title>
      <link>https://forem.com/slashgear_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/slashgear_"/>
    <language>en</language>
    <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://forem.com/slashgear_/managing-terraform-modules-with-nx-monorepo-4jfa</link>
      <guid>https://forem.com/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://forem.com/slashgear_/how-to-automatically-merge-dependabot-pull-requests-with-github-actions--30pe</link>
      <guid>https://forem.com/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;/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://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.slashgear.dev%2F8483cc022eacd31176dd8f0659c3d658%2Fclick.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.slashgear.dev%2F8483cc022eacd31176dd8f0659c3d658%2Fclick.gif" alt="Animation of a hand clicking frantically"&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;/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://forem.com/bedrock/best-practices-for-web-application-maintenance-2hgl</link>
      <guid>https://forem.com/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://media.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://media.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"&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://media.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://media.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"&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://media.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://media.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"&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://media.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%2F9pgsd2t3t620aivnba6e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F9pgsd2t3t620aivnba6e.gif" alt="the recipe of the magic potion of panoramix is lost because secret"&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://media.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://media.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"&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://media.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%2Fx52jbcyoxk2eqdjcmyav.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fx52jbcyoxk2eqdjcmyav.gif" alt="Example of functional test execution"&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://media.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://media.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"&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://media.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://media.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"&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://media.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://media.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"&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://media.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://media.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"&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://media.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%2Fsa03ytio1zueetbeeuxb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fsa03ytio1zueetbeeuxb.gif" alt="general alert"&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://media.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%2Fdj9koo9ksvhoopaidmst.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fdj9koo9ksvhoopaidmst.gif" alt="It will hold!"&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://forem.com/slashgear_/how-to-setup-end-to-end-tests-with-webdriverio-on-github-action-f9n</link>
      <guid>https://forem.com/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://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fslashgear.github.io%2F98f39f6c4c1dabf691434adfc56a6ff9%2Fsetup.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fslashgear.github.io%2F98f39f6c4c1dabf691434adfc56a6ff9%2Fsetup.gif" alt="exemple screen captured of the CLI output"&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://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fslashgear.github.io%2F91922b6797f633320658a98c56f955cf%2Ftest-run.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fslashgear.github.io%2F91922b6797f633320658a98c56f955cf%2Ftest-run.gif" alt="Here is a gif of what happened in my Chrome browser"&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://media.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://media.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"&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://forem.com/slashgear_/should-we-replace-webpack-by-snowpack-11de</link>
      <guid>https://forem.com/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://media.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://media.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"&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://media.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://media.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"&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://forem.com/slashgear_/vite-a-new-webpack-killer-162g</link>
      <guid>https://forem.com/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://media.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://media.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"&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://media.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://media.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"&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://media.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://media.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"&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://media.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://media.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"&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://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fslashgear.github.io%2F0e9e962814ce9f4e10f1810913a40893%2Fsurprise.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fslashgear.github.io%2F0e9e962814ce9f4e10f1810913a40893%2Fsurprise.gif" alt="surprise"&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://forem.com/slashgear_/esbuild-the-incredibly-fast-and-promising-bundler-pbh</link>
      <guid>https://forem.com/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"&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="nx"&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="nx"&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"&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"&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"&gt;more explanations in the doc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eJBtY33H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0yh4azfjo27eyasr46d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eJBtY33H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0yh4azfjo27eyasr46d6.png" alt="esbuild architecture diagram"&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="nx"&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="nx"&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="nx"&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="nx"&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"&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/"&gt;Vite&lt;/a&gt; or &lt;a href="https://www.snowpack.dev/"&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://forem.com/slashgear_/advent-of-code-2020-my-solutions-with-js-4na8</link>
      <guid>https://forem.com/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://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Slashgear"&gt;
        Slashgear
      &lt;/a&gt; / &lt;a href="https://github.com/Slashgear/advent-of-code"&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;h1&gt;
advent-of-code by &lt;a href="https://github.com/Slashgear"&gt;Slashgear&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;In this repository you can find all the &lt;a href="https://adventofcode.com/" rel="nofollow"&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;pre&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;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Slashgear/advent-of-code"&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"&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"&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"&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"&gt;2020 day 4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>adventofcode</category>
    </item>
    <item>
      <title>Github Action: How to parallelize tests dynamically by folder?</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Tue, 01 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/slashgear_/github-action-how-to-parallelize-tests-dynamically-by-folder-3hen</link>
      <guid>https://forem.com/slashgear_/github-action-how-to-parallelize-tests-dynamically-by-folder-3hen</guid>
      <description>&lt;p&gt;Once again, I gave myself a challenge to learn how to use Github Action. I tried to see if it was possible to dynamically launch a job for each folder in a directory.&lt;/p&gt;

&lt;p&gt;We often want to run tests in parallel to reduce the time of continuous integration. Often tools like &lt;code&gt;Jest&lt;/code&gt; or &lt;code&gt;CucumberJS&lt;/code&gt; have parallelization features, but these often face the limits of the power available on the machines on which you want to run them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tests/
  user/
    foo.feature
    bar.feature
  account/
    example.feature
  live/
    a.feature
    video.feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wondered if it wouldn’t be more efficient to run the tests in parallel directly with the CI tool so that only small &lt;em&gt;runners&lt;/em&gt; would be needed. In the previous example, I would like to be able to run in parallel on a separate &lt;em&gt;runner&lt;/em&gt; for each folder in &lt;code&gt;test/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Solution with &lt;em&gt;Github action&lt;/em&gt;
&lt;/h2&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;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;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;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Job that list subdirectories&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;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.set-dirs.outputs.dir }}&lt;/span&gt; &lt;span class="c1"&gt;# generate output name dir by using inner step output&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@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;set-dirs&lt;/span&gt; &lt;span class="c1"&gt;# Give it an id to handle to get step outputs in the outputs key above&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;echo "::set-output name=dir::$(ls -d */ | jq -R -s -c 'split("\n")[:-1]')"&lt;/span&gt;
        &lt;span class="c1"&gt;# Define step output named dir base on ls command transformed to JSON thanks to jq&lt;/span&gt;
  &lt;span class="na"&gt;loop&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;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;directories&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Depends on previous job&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{fromJson(needs.directories.outputs.dir)}}&lt;/span&gt; &lt;span class="c1"&gt;# List matrix strategy from directories dynamically&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;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{matrix.dir}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s up to you to adapt it to your needs!&lt;/p&gt;

</description>
      <category>github</category>
      <category>ci</category>
    </item>
    <item>
      <title>Github Action: How to dynamically run step on each PR label ?</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Sun, 29 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/slashgear_/github-action-how-to-dynamically-run-step-on-each-pr-label-3f8h</link>
      <guid>https://forem.com/slashgear_/github-action-how-to-dynamically-run-step-on-each-pr-label-3f8h</guid>
      <description>&lt;p&gt;For some time now I’ve been playing with github actions to see if I can easily reproduce behaviors I apply in my current CI tool. I wanted to parallelize tasks on each label of a pull request. In this article, I will show you a possible way to do it!&lt;/p&gt;

&lt;p&gt;In my work at &lt;a href="https://www.bedrockstreaming.com/" rel="noopener noreferrer"&gt;Bedrock&lt;/a&gt;, we currently use Jenkins to manage our ongoing application integration and deployment. Beyond the various concerns of using groovy with pipeline syntax 😅, we have to admit that we manage to do some pretty complex workflows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwuab225zvtojfqe6vpin.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwuab225zvtojfqe6vpin.jpg" alt="example of complex Continous Integration workflow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deciding to see if it would be easy for me to reproduce the steps of these pipelines with github action, I decided to try its features. Among other things by publishing &lt;a href="https://github.com/Slashgear/action-check-pr-title" rel="noopener noreferrer"&gt;a Github Action to check the title of a PR according to a regexp.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;First you need to create a workflow based on &lt;code&gt;pull_request&lt;/code&gt; events and you can even specify the types of events.&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;Example workflow&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;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;labeled&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;unlabeled&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add a job that will set up a parallelization strategy based on the labels of the github event.&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;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;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.event.pull_request.labels.*.name}}&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@v2&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 a one-line script&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;echo Hello, world from "${{matrix.label}}"!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will automatically run the steps in parallel on all the labels of each Pull Request. 🎉 However, &lt;code&gt;strategy.matrix.label&lt;/code&gt; does not accept an empty array as a value.😢&lt;/p&gt;

&lt;p&gt;The job should therefore be conditional so that it is only run if &lt;code&gt;${{github.event.pull_request.labels.*.name}}&lt;/code&gt; is not empty. There is an &lt;code&gt;if&lt;/code&gt; parameter in the jobs that allows to do this, but unfortunately it is interpreted after checking the value of &lt;code&gt;strategy.matrix.label&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The solution I found is to do this check in a previous job. This is an example of a workflow that dynamically launches actions on all the labels of each PR.&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;Example workflow&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;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;labeled&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;unlabeled&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&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;checkLabels&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;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.event.pull_request.labels.*.name[0]}}&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;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Check labels is not empty&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;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;checkLabels&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{github.event.pull_request.labels.*.name}}&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@v2&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 a one-line script&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;echo Hello, world from "${{matrix.label}}"!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s up to you to adapt it to your needs!&lt;/p&gt;

</description>
      <category>github</category>
      <category>ci</category>
    </item>
    <item>
      <title>How to deploy on Github Pages with Travis CI?</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Tue, 26 May 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/slashgear_/how-to-deploy-on-github-pages-with-travis-ci-4p70</link>
      <guid>https://forem.com/slashgear_/how-to-deploy-on-github-pages-with-travis-ci-4p70</guid>
      <description>&lt;p&gt;Github Pages is a solution to host static websites directly in your Github repository by pushing site files to &lt;code&gt;gh-pages&lt;/code&gt; branch.I use this feature to host &lt;a href="https://slashgear.github.io"&gt;this blog&lt;/a&gt;.It is free and very useful for your own site or open source documentation.Here is a tutorial of how to publish Gatsby website with travis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make sure to have a public github repository with your static site and a travis-ci.org account.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Add travis file to your project
&lt;/h2&gt;

&lt;p&gt;Define basic travis-ci configuration to build and push you site on &lt;code&gt;gh-pages&lt;/code&gt; page on each new commit on master.&lt;em&gt;Add &lt;code&gt;.travis-ci.yml&lt;/code&gt; file in the root directory of your git project.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Edit this file with your build configuration.&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;# This is the required part containing the build step for gatsby&lt;/span&gt;
&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_js&lt;/span&gt;
&lt;span class="na"&gt;node_js&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;12'&lt;/span&gt;
&lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;yarn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.cache&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;yarn build&lt;/span&gt; &lt;span class="c1"&gt;# Generate static HTML files&lt;/span&gt;

&lt;span class="c1"&gt;# Here is the magic part&lt;/span&gt;
&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Use Github pages deploy process&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pages&lt;/span&gt;
  &lt;span class="c1"&gt;# Keep builded pages&lt;/span&gt;
  &lt;span class="na"&gt;skip-cleanup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;# Directory where your generated files are located&lt;/span&gt;
  &lt;span class="na"&gt;local_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
  &lt;span class="c1"&gt;# Github security/auth token&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;$GITHUB_TOKEN&lt;/span&gt;
  &lt;span class="c1"&gt;# Incremental commit to keep old build/files from previous deployments&lt;/span&gt;
  &lt;span class="na"&gt;keep-history&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;# Git branch on which it should deploy (master, gh-pages, foo...)&lt;/span&gt;
  &lt;span class="na"&gt;target_branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gh-pages&lt;/span&gt;
  &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Which branch on commit/push will trigger deployment&lt;/span&gt;
    &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Take a look at &lt;a href="https://docs.travis-ci.com/user/deployment/"&gt;all deploy providers available with travis&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Generate Github personal token
&lt;/h2&gt;

&lt;p&gt;Generate a Github token on &lt;a href="https://github.com/settings/tokens/new"&gt;this page&lt;/a&gt; with repository permission and keep it safe.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TzkVPioV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/smppnmb785k9z5tmp1va.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TzkVPioV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/smppnmb785k9z5tmp1va.png" alt="github token generate page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Configure your travis job
&lt;/h2&gt;

&lt;p&gt;Toggle your github repository to travis listed project on &lt;a href="https://travis-ci.org/account/repositories"&gt;this page&lt;/a&gt;.Then on your project travis page, go to &lt;code&gt;More options &amp;gt; Settings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k9l2SDbS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mll3ik2zg49ak73rfm3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k9l2SDbS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mll3ik2zg49ak73rfm3w.png" alt="project options"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;Environment Variables&lt;/code&gt; section, add variable named &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; and use generated token from previous step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p4bPHt9f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x8kvf7vf6bkup2tssesi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p4bPHt9f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x8kvf7vf6bkup2tssesi.png" alt="env vars"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Activate Github Pages feature for your repository
&lt;/h2&gt;

&lt;p&gt;In your github repository settings page, be sure to activate github pages feature in &lt;code&gt;Github Pages&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VZCyjvHs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jhslthozqwnexuxm90za.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VZCyjvHs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jhslthozqwnexuxm90za.png" alt="github page settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Save it, and now it is done !You are good to go, your next commit pushed on master branch will trigger deployment of your site.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hope this will help you configure travis deployment for your project.&lt;/p&gt;

&lt;p&gt;If you liked it, I’ll try to do the same tutorial with &lt;em&gt;Github Action&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>github</category>
      <category>travis</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Announcing use-reduced-motion</title>
      <dc:creator>Antoine Caron</dc:creator>
      <pubDate>Mon, 04 May 2020 12:01:05 +0000</pubDate>
      <link>https://forem.com/slashgear_/announcing-use-reduced-motion-3dh3</link>
      <guid>https://forem.com/slashgear_/announcing-use-reduced-motion-3dh3</guid>
      <description>&lt;h2&gt;
  
  
  Everything always starts with an idea
&lt;/h2&gt;

&lt;p&gt;Last week, as I was setting up the dark mode on &lt;a href="https://slashgear.github.io/"&gt;my personal website&lt;/a&gt;, I got motivated to work on a new package for the React community.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;" Well, somebody shared a hook to handle the &lt;code&gt;prefers-color-scheme&lt;/code&gt; feature in browsers, that's super handy."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In just a few minutes, I was able to integrate this great feature without getting in my head.&lt;br&gt;
That's when I thought of that &lt;a href="https://web.dev/prefers-reduced-motion/"&gt;awesome blog post about a new feature for accessibility&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At the time, while reading this article, I found out that some users may feel uncomfortable consulting web pages that "wiggle".&lt;br&gt;
Indeed, elements of a page that move, zoom or change colors can greatly disturb the use and understanding of the content of our pages.&lt;br&gt;
This may not be the case for you, but many users are in this situation.&lt;/p&gt;

&lt;p&gt;In order to allow you to better understand the problem, I suggest a small experiment with a very short video.&lt;br&gt;
Focus on the people dressed in white and try to count the number of passes.&lt;/p&gt;

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

&lt;p&gt;Do you understand now?&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;"superb onboarding animation"&lt;/em&gt; may be very beautiful, but it is very disturbing for some of your users.&lt;br&gt;
They find themselves in the same situation as you do with this video.&lt;br&gt;
The moving elements make it difficult to capture all the information you want to convey.&lt;/p&gt;

&lt;p&gt;Fortunately, OS and browsers have become aware of accessibility issues and now provide tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is therefore our responsibility today to integrate these solutions to avoid putting some of our users in a situation of handicap.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;media query&lt;/em&gt; allows you to stop your animations for users who wish to do so.&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="c"&gt;/*
  If the user has expressed their preference for
  reduced motion, then don't use animations on buttons.
*/&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;The &lt;a href="https://caniuse.com/#feat=prefers-reduced-motion"&gt;support for this feature&lt;/a&gt; is even very correct.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4tcSWMh0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tegmk28n1ctin5um0xsz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4tcSWMh0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tegmk28n1ctin5um0xsz.png" alt="support for this feature in may 2020"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a video demo of how this feature works, taken from &lt;a href="https://web.dev/prefers-reduced-motion/"&gt;the article quoted above&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://storage.googleapis.com/web-dev-assets/prefers-reduced-motion/prefers-reduced-motion.mp4"&gt;https://storage.googleapis.com/web-dev-assets/prefers-reduced-motion/prefers-reduced-motion.mp4&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Where is the package and how to use it?
&lt;/h2&gt;

&lt;p&gt;It's very nice this &lt;em&gt;media query&lt;/em&gt; but in some cases, the animations I use on my sites are managed by JavaScript.&lt;br&gt;
Fortunately, we have solutions to track the use of the &lt;em&gt;media query&lt;/em&gt; in the browser.&lt;/p&gt;

&lt;p&gt;To make them easier to use with React, I've integrated them into a Hook in the way of "use-dark-mode".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/use-reduced-motion"&gt;https://www.npmjs.com/package/use-reduced-motion&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To install it in your project, nothing could be simpler:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;use-reduced-motion
&lt;span class="c"&gt;# or&lt;/span&gt;
yarn add use-reduced-motion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then you just have to use it in one of your components:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useReducedMotion&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;use-reduced-motion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AnimatedDiv&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;../somewhere&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyExampleComponent&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefersReducedMotion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useReducedMotion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AnimatedDiv&lt;/span&gt; &lt;span class="na"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;prefersReducedMotion&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I invite you to test here with your browser/OS, the following animation will stop automatically.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/pi966"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Feel free to share this article if you liked it, any contribution to the package is welcome.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Slashgear"&gt;
        Slashgear
      &lt;/a&gt; / &lt;a href="https://github.com/Slashgear/use-reduced-motion"&gt;
        use-reduced-motion
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      React Hook to detect option that reduce browser animation and motion for A11Y
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Welcome to use-reduced-motion 👋
&lt;/h1&gt;
&lt;p&gt;
  &lt;a href="https://www.npmjs.com/package/use-reduced-motion" rel="nofollow"&gt;
    &lt;img alt="Version" src="https://camo.githubusercontent.com/1481279eb44415e93beddf10192f450a09f76e50a7154e7cd633c6e94ff09e1c/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f7573652d726564756365642d6d6f74696f6e2e737667"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/Slashgear/use-reduce-motion#readme"&gt;
    &lt;img alt="Documentation" src="https://camo.githubusercontent.com/335378d3b5837f055d0c9bcab2850a8845250dbd39b91e91a7fee77b50a96cfb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646f63756d656e746174696f6e2d7965732d627269676874677265656e2e737667"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/Slashgear/use-reduce-motion/graphs/commit-activity"&gt;
    &lt;img alt="Maintenance" src="https://camo.githubusercontent.com/6e4da91cb02711349e6b9d0aba6a767362818c1d17891a02f06fded4415f6172/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d61696e7461696e65642533462d7965732d677265656e2e737667"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/Slashgear/use-reduce-motion/blob/master/LICENSE"&gt;
    &lt;img alt="License: MIT" src="https://camo.githubusercontent.com/5e963eef9b3f9aa95d9c0f5079322df36f7668ab743c7a90e823c40d19e040c6/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f536c617368676561722f7573652d7265647563652d6d6f74696f6e"&gt;
  &lt;/a&gt;
  &lt;a href="https://twitter.com/Slashgear%5C_" rel="nofollow"&gt;
    &lt;img alt="Twitter: Slashgear_" src="https://camo.githubusercontent.com/6e79ad3902f4bc2ebccce1c1c45411586b6214d0fe01c66aeee7c3a3c664aff0/68747470733a2f2f696d672e736869656c64732e696f2f747769747465722f666f6c6c6f772f536c617368676561725f2e7376673f7374796c653d736f6369616c"&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;React hook to detect user reduced motion feature&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Install&lt;/h2&gt;
&lt;div class="highlight highlight-source-shell js-code-highlight"&gt;
&lt;pre&gt;npm install use-reduced-motion
yarn add use-reduced-motion&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;p&gt;Use directly in your component.
Pass the boolean value to your JS animated component to stop it.&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;React&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"react"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;useReducedMotion&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"use-reduced-motion"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-v"&gt;AnimatedDiv&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"../somewhere"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;export&lt;/span&gt; &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-v"&gt;MyExampleComponent&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;stopMotion&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;useReducedMotion&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;AnimatedDiv&lt;/span&gt; &lt;span class="pl-c1"&gt;pause&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;stopMotion&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;/&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
Author&lt;/h2&gt;
&lt;p&gt;👤 &lt;strong&gt;Antoine CARON &lt;a href="https://raw.githubusercontent.com/Slashgear/use-reduced-motion/master/mailto:antoine395.caron@gmail.com"&gt;antoine395.caron@gmail.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="http://slashgear.github.io/" rel="nofollow"&gt;http://slashgear.github.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/Slashgear_" rel="nofollow"&gt;@Slashgear_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Github: &lt;a href="https://github.com/Slashgear"&gt;@Slashgear&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Show your support&lt;/h2&gt;
&lt;p&gt;Give a ⭐️ if this project helped you!&lt;/p&gt;
&lt;h2&gt;
📝 License&lt;/h2&gt;
&lt;p&gt;Copyright © 2020 &lt;a href="https://github.com/Slashgear"&gt;Antoine CARON &lt;/a&gt;&lt;a href="https://raw.githubusercontent.com/Slashgear/use-reduced-motion/master/mailto:antoine395.caron@gmail.com"&gt;antoine395.caron@gmail.com&lt;/a&gt;.&lt;br&gt;
This project is &lt;a href="https://github.com/Slashgear/use-reduce-motion/blob/master/LICENSE"&gt;MIT&lt;/a&gt; licensed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This README was generated with ❤️ by &lt;a href="https://github.com/kefranabg/readme-md-generator"&gt;readme-md-generator&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Slashgear/use-reduced-motion"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.reactjs.org/docs/hooks-intro.html"&gt;React Hook Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/donavon/use-dark-mode"&gt;"use-dark-mode"&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Image credit &lt;a href="https://undraw.co/"&gt;unDraw&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>react</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
