<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: greenroach</title>
    <description>The latest articles on DEV Community by greenroach (@greenroach).</description>
    <link>https://dev.to/greenroach</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%2F641778%2F2a11b099-8e8e-4770-9b56-55dcd8260a41.jpeg</url>
      <title>DEV Community: greenroach</title>
      <link>https://dev.to/greenroach</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/greenroach"/>
    <language>en</language>
    <item>
      <title>Detecting Circular Dependencies in a React/TypeScript App using Madge</title>
      <dc:creator>greenroach</dc:creator>
      <pubDate>Sat, 21 Sep 2024 10:57:48 +0000</pubDate>
      <link>https://dev.to/greenroach/detecting-circular-dependencies-in-a-reacttypescript-app-using-madge-229</link>
      <guid>https://dev.to/greenroach/detecting-circular-dependencies-in-a-reacttypescript-app-using-madge-229</guid>
      <description>&lt;p&gt;When building a React/TypeScript application, maintaining clean and modular code is important. As the codebase grows, circular dependencies can easily slip in, causing unexpected bugs, performance issues, and making the app harder to maintain.&lt;/p&gt;

&lt;p&gt;Circular dependencies can be a real headache for developers. I’ve faced this myself while working on a legacy project riddled with them. In this article, I’ll share my experience for tracking circular dependencies in CI environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Are Circular Dependencies a Problem?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unpredictable Behavior&lt;/strong&gt;: JavaScript may load incomplete or undefined modules due to the circular reference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard to Debug&lt;/strong&gt;: Circular dependencies can lead to subtle bugs that are difficult to trace.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introducing Madge
&lt;/h2&gt;

&lt;p&gt;Fortunately, there's a tool called &lt;strong&gt;Madge&lt;/strong&gt; that can detect circular dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Madge
&lt;/h3&gt;

&lt;p&gt;To start using Madge, install it via &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt;:&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; &lt;span class="nt"&gt;--global&lt;/span&gt; madge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn global add madge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the root of your project directory, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;madge &lt;span class="nt"&gt;--circular&lt;/span&gt; src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command analyzes the &lt;code&gt;src/&lt;/code&gt; directory and outputs any circular dependencies it finds.&lt;/p&gt;

&lt;p&gt;Sample Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✖ Found 2 circular dependencies!

1&lt;span class="o"&gt;)&lt;/span&gt; moduleA.ts -&amp;gt; moduleB.ts -&amp;gt; moduleA.ts
2&lt;span class="o"&gt;)&lt;/span&gt; moduleC.ts -&amp;gt; moduleD.ts -&amp;gt; moduleE.ts -&amp;gt; moduleC.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring
&lt;/h3&gt;

&lt;p&gt;To ensure Madge works properly with TypeScript  and path mappings specify file extensions and point Madge to your &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;madge &lt;span class="nt"&gt;--circular&lt;/span&gt; &lt;span class="nt"&gt;--ts-config&lt;/span&gt; ./tsconfig.json &lt;span class="nt"&gt;--extensions&lt;/span&gt; ts,tsx src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Command Breakdown:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--circular&lt;/code&gt;: Detects circular dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--ts-config ./tsconfig.json&lt;/code&gt;: Directs Madge to use your TypeScript configuration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--extensions ts,tsx&lt;/code&gt;: Ensures Madge scans &lt;code&gt;.ts&lt;/code&gt; and &lt;code&gt;.tsx&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/&lt;/code&gt;: The directory to scan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also you might Madge to ignore TypeScript type imports and dynamic (async) imports. To achieve that you can add the following configuration to &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="nl"&gt;"madge"&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;"detectiveOptions"&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;"ts"&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;"skipAsyncImports"&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;"skipTypeImports"&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="nl"&gt;"tsx"&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;"skipAsyncImports"&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;"skipTypeImports"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automating Circular Dependency Detection
&lt;/h2&gt;

&lt;p&gt;The goal is to catch circular dependencies before they are merged into the main branch.&lt;/p&gt;

&lt;p&gt;Step 1: Creating a Bash Script for Circular Dependency Checks&lt;/p&gt;

&lt;p&gt;First, let's create a script that will check for circular dependencies and set a limit on how many are allowed. This script can be particularly useful if you’re dealing with an existing project with legacy circular dependencies, allowing you to monitor that no new ones are added:&lt;/p&gt;

&lt;p&gt;Create a Bash script (&lt;code&gt;check_circular_deps.sh&lt;/code&gt;) to run Madge with an error threshold:&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;#!/bin/bash&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; madge &amp;amp;&amp;gt; /dev/null
&lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Madge is not installed. Please install it first."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;error_limit&amp;gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;error_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;

&lt;span class="nv"&gt;stderr_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;madge &lt;span class="nt"&gt;--circular&lt;/span&gt; &lt;span class="nt"&gt;--no-spinner&lt;/span&gt; &lt;span class="nt"&gt;--no-color&lt;/span&gt; &lt;span class="nt"&gt;--ts-config&lt;/span&gt; ./tsconfig.json &lt;span class="nt"&gt;--extensions&lt;/span&gt; ts,tsx ./src/index.tsx 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stderr_output&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'Found [0-9]\+ circular dependencies'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;circular_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stderr_output&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'Found [0-9]\+ circular dependencies'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]\+'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nv"&gt;circular_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Number of circular dependencies: &lt;/span&gt;&lt;span class="nv"&gt;$circular_count&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$circular_count&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$error_limit&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Circular dependency count (&lt;/span&gt;&lt;span class="nv"&gt;$circular_count&lt;/span&gt;&lt;span class="s2"&gt;) exceeds the limit (&lt;/span&gt;&lt;span class="nv"&gt;$error_limit&lt;/span&gt;&lt;span class="s2"&gt;)."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Circular dependency count is within the limit."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Adding a Script to package.json
&lt;/h3&gt;

&lt;p&gt;To make it easier to run the check within the CI pipeline, add a script to your package.json:&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;"check-circular-deps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bash ./scripts/check_circular_deps.sh 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;In this example, the number 0 is the threshold for allowed circular dependencies. Adjust this number based on your project’s current state. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Setting Up CI Pipeline
&lt;/h3&gt;

&lt;p&gt;Next, modify your pipelines configuration to include a job that runs this circular dependency check.&lt;/p&gt;

&lt;p&gt;Here’s an example configuration for GitLab:&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;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;test&lt;/span&gt;

&lt;span class="na"&gt;circular-dependency-check&lt;/span&gt;&lt;span class="pi"&gt;:&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;test&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;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 install madge -g&lt;/span&gt; &lt;span class="c1"&gt;# Install Madge globally&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;          &lt;span class="c1"&gt;# Install project dependencies&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run check-circular-deps&lt;/span&gt; &lt;span class="c1"&gt;# Run the circular dependency check&lt;/span&gt;
    &lt;span class="na"&gt;allow_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# Fail the pipeline if circular dependencies exceed the limit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Circular dependencies can sneak into your code and cause all sorts of problems. They can lead to weird bugs, slow down your app, and make it a pain to work with. Luckily, you can automate the process of finding and fixing these pesky dependencies, making your app more stable and easier to maintain.&lt;/p&gt;

</description>
      <category>blog</category>
      <category>typescript</category>
      <category>react</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
