DEV Community

Cover image for Detecting Circular Dependencies in a React/TypeScript App using Madge
greenroach
greenroach

Posted on

Detecting Circular Dependencies in a React/TypeScript App using Madge

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.

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.

Why Are Circular Dependencies a Problem?

  1. Unpredictable Behavior: JavaScript may load incomplete or undefined modules due to the circular reference.
  2. Hard to Debug: Circular dependencies can lead to subtle bugs that are difficult to trace.

Introducing Madge

Fortunately, there's a tool called Madge that can detect circular dependencies.

Setting Up Madge

To start using Madge, install it via npm or yarn:

npm install --global madge
Enter fullscreen mode Exit fullscreen mode

or

yarn global add madge
Enter fullscreen mode Exit fullscreen mode

In the root of your project directory, run the following command:

madge --circular src/
Enter fullscreen mode Exit fullscreen mode

This command analyzes the src/ directory and outputs any circular dependencies it finds.

Sample Output:

✖ Found 2 circular dependencies!

1) moduleA.ts -> moduleB.ts -> moduleA.ts
2) moduleC.ts -> moduleD.ts -> moduleE.ts -> moduleC.ts
Enter fullscreen mode Exit fullscreen mode

Configuring

To ensure Madge works properly with TypeScript and path mappings specify file extensions and point Madge to your tsconfig.json.

madge --circular --ts-config ./tsconfig.json --extensions ts,tsx src/
Enter fullscreen mode Exit fullscreen mode

Command Breakdown:

  • --circular: Detects circular dependencies.
  • --ts-config ./tsconfig.json: Directs Madge to use your TypeScript configuration.
  • --extensions ts,tsx: Ensures Madge scans .ts and .tsx files.
  • src/: The directory to scan.

Also you might Madge to ignore TypeScript type imports and dynamic (async) imports. To achieve that you can add the following configuration to package.json.

"madge": {
    "detectiveOptions": {
        "ts": {
            "skipAsyncImports": true,
            "skipTypeImports": true
        },
        "tsx": {
            "skipAsyncImports": true,
            "skipTypeImports": true
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Automating Circular Dependency Detection

The goal is to catch circular dependencies before they are merged into the main branch.

Step 1: Creating a Bash Script for Circular Dependency Checks

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:

Create a Bash script (check_circular_deps.sh) to run Madge with an error threshold:

#!/bin/bash

if ! command -v madge &> /dev/null
then
    echo "Madge is not installed. Please install it first."
    exit 1
fi

if [ -z "$1" ]; then
    echo "Usage: $0 <error_limit>"
    exit 1
fi

error_limit=$1

stderr_output=$(madge --circular --no-spinner --no-color --ts-config ./tsconfig.json --extensions ts,tsx ./src/index.tsx 2>&1 >/dev/null)

if echo "$stderr_output" | grep -q 'Found [0-9]\+ circular dependencies'; then
    circular_count=$(echo "$stderr_output" | grep -o 'Found [0-9]\+ circular dependencies' | grep -o '[0-9]\+')
else
    circular_count=0
fi

echo "Number of circular dependencies: $circular_count"

if [ "$circular_count" -gt "$error_limit" ]; then
    echo "Error: Circular dependency count ($circular_count) exceeds the limit ($error_limit)."
    exit 1
fi

echo "Circular dependency count is within the limit."
Enter fullscreen mode Exit fullscreen mode

Step 2: Adding a Script to package.json

To make it easier to run the check within the CI pipeline, add a script to your package.json:

{
  "scripts": {
    "check-circular-deps": "bash ./scripts/check_circular_deps.sh 0"
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the number 0 is the threshold for allowed circular dependencies. Adjust this number based on your project’s current state.

Step 3: Setting Up CI Pipeline

Next, modify your pipelines configuration to include a job that runs this circular dependency check.

Here’s an example configuration for GitLab:

stages:
  - test

circular-dependency-check:
    stage: test
    image: node:18
    script:
        - npm install madge -g # Install Madge globally
        - npm install          # Install project dependencies
        - npm run check-circular-deps # Run the circular dependency check
    allow_failure: false # Fail the pipeline if circular dependencies exceed the limit
Enter fullscreen mode Exit fullscreen mode

Conclusion

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.

Top comments (0)