How to detect and avoid cyclic dependencies in javascript

In many programming languages, you might heard about circular dependencies (a.k.a as cyclic dependencies).  But if you are new to this terminology then let me briefly explain what that is, how to detect and how to avoid that in your code.

As per wikipedia “let me briefly explain what that is and why would you prefer to avoid that in your code.”  The below diagram depicts how these circular dependencies related as an example,

Many developers assume that circular dependencies are anti-design pattern and bad design, but they are often required in a complex software application. So if your application contains circular dependency then it doesn’t always imply a bug. In case, when there is a chance of bug or problem then it is developer responsibility to detect and avoid them.

There are two possible options to detect cyclic dependencies in your javascript project

Circular Dependency Plugin: 

This plugin is used to detect circular dependencies when bundling with webpack bundler. The compatible version of this plugin should be used based on webpack version. The latest version5.0 of this plugin  supports webpack 4.x where as  4.0 version supports Webpack 3.x version. Find more details about the npm module here

The plugin is quite mature  and I have been used in one of a Vue project to detect the dependencies. It detected indirect dependencies caused by Vue Router package.

The usage of this plugin is very simple. You just need to import the plugin and configure it similar        to any other plugin.

// webpack.config.js

const CircularDependencyPlugin = require('circular-dependency-plugin')

module.exports = {

  entry: "./src/index",

  plugins: [

    new CircularDependencyPlugin({

      // exclude detection of files based on a RegExp

      exclude: /a\.js|node_modules/,

      // add errors to webpack instead of warnings

      failOnError: true,

      // allow import cycles that include an asyncronous import,

      // e.g. via import(/* webpackMode: "weak" */ './file.js')

      allowAsyncCycles: false,

      // set the current working directory for displaying module paths

      cwd: process.cwd(),

    })

  ]

}

The customization for cyclic dependencies can be achieved using three callbacks: onStart, onDetect and onEnd. For example, the default messages can be customized by indicating the beginning, cyclic modules path and completion of detecting dependencies as below,

new CircularDependencyPlugin({
      // `onStart` is called before the cycle detection starts
      onStart({ compilation }) {
        console.log('start detecting webpack modules cycles');
      },

      // `onDetected` is called for each module that is cyclical
      onDetected({ module: webpackModuleRecord, paths, compilation }) {
        // `paths` will be an Array of the relative module paths that make up the cycle
        // `module` will be the module record generated by webpack that caused the cycle
        compilation.errors.push(new Error(paths.join(' -> ')))
      },

      // `onEnd` is called before the cycle detection ends
      onEnd({ compilation }) {
        console.log('end detecting webpack modules cycles');
      },
    })

Currently it only provides metrics through messages either in warnings or errors.

Madge:

Apart from showing the circular dependencies information, it can act as a developer tool for generating a visual graph of your module dependencies. It would be easy to find the dependencies relation in the complex application. Find more details about the npm module here

You can install this package using below npm command,

npm --save-dev install madge

if you want to generate visual graphs either in SVG or DOT format then you need to install Graphviz software,

$ apt-get install graphviz

Now it is ready to use madge in your application by creating it as npm script. The commands for finding circular dependencies and creating the visual graph for dependencies would be as follows,

madge --circular path/src/app.js
madge --image graph.svg src/images

The example visual graph of module dependencies would be as below. The red color boxes indicates circular dependencies.

After detecting the circular dependencies, the next step is going to be how to break the cyclic dependencies. You need to move the code snippets to some other sharable file to break dependencies among modules. This might be a time taking process based on type of complexity in the application.

Note: I reported one issue related to dynamic imports support and the fix is available in the latest version.

Conclusion: Even though there are no standard tools or packages available to detect the circular dependencies, both circular dependency plugin and madge package provides flexible API to find out these cyclic dependencies in the large to medium scale applications. I believe that each application should have a check to find out and avoid these dependencies as much in the future.

Leave a Reply

Your email address will not be published. Required fields are marked *