or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-circular-dependency-plugin

Webpack plugin that detects modules with circular dependencies during bundling.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/circular-dependency-plugin@5.2.x

To install, run

npx @tessl/cli install tessl/npm-circular-dependency-plugin@5.2.0

index.mddocs/

Circular Dependency Plugin

Circular Dependency Plugin is a webpack plugin that detects modules with circular dependencies during the bundling process. It provides comprehensive configuration options for handling circular dependencies including pattern-based inclusion/exclusion, customizable error/warning handling, async import cycle support, and lifecycle hooks for advanced integration scenarios.

Package Information

  • Package Name: circular-dependency-plugin
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install circular-dependency-plugin
  • Webpack Compatibility: 4.0.1 and greater
  • Node.js Compatibility: 6.0.0 and greater

Core Imports

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

Basic Usage

// 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/,
      // include specific files based on a RegExp
      include: /dir/,
      // add errors to webpack instead of warnings
      failOnError: true,
      // allow import cycles that include an asyncronous import
      allowAsyncCycles: false,
      // set the current working directory for displaying module paths
      cwd: process.cwd(),
    })
  ]
}

Architecture

The Circular Dependency Plugin integrates with webpack's plugin system through the standard webpack plugin interface. It operates during the compilation phase by:

  • Plugin Registration: Hooks into webpack's compilation.hooks.optimizeModules phase
  • Module Analysis: Iterates through all modules in the module graph after they've been processed
  • Dependency Traversal: Recursively follows module dependencies to detect cycles using depth-first search
  • Webpack Compatibility: Supports both webpack 4 and webpack 5 through version-specific module graph access
  • Lifecycle Integration: Provides hooks (onStart, onDetected, onEnd) for custom integration with build processes

The plugin works by maintaining a cache of visited modules during traversal and detecting when a dependency path returns to the initial module, indicating a circular dependency.

Capabilities

Plugin Constructor

Creates a new instance of the CircularDependencyPlugin with optional configuration.

/**
 * Creates a webpack plugin instance for detecting circular dependencies
 * @param {Object} options - Configuration options for the plugin
 */
new CircularDependencyPlugin(options?: PluginOptions);

interface PluginOptions {
  /** Pattern to exclude files from circular dependency detection */
  exclude?: RegExp;
  /** Pattern to include files in circular dependency detection */
  include?: RegExp;
  /** Whether to emit errors instead of warnings when cycles detected */
  failOnError?: boolean;
  /** Whether to allow circular dependencies that include async imports (e.g., import(/* webpackMode: "weak" */ './module')) */
  allowAsyncCycles?: boolean;
  /** Callback function called when circular dependency is detected */
  onDetected?: (info: DetectionInfo) => void;
  /** Callback function called before cycle detection starts */
  onStart?: (info: CompilationInfo) => void;
  /** Callback function called after cycle detection ends */
  onEnd?: (info: CompilationInfo) => void;
  /** Current working directory for displaying relative module paths */
  cwd?: string;
}

interface DetectionInfo {
  /** webpack module record that caused the cycle */
  module: any;
  /** Array of relative module paths that make up the cycle */
  paths: string[];
  /** webpack compilation object */
  compilation: any;
}

interface CompilationInfo {
  /** webpack compilation object */
  compilation: any;
}

Usage Examples:

// Basic usage with default options
new CircularDependencyPlugin()

// Custom configuration
new CircularDependencyPlugin({
  exclude: /node_modules/,
  include: /src/,
  failOnError: true,
  allowAsyncCycles: false,
  cwd: process.cwd()
})

Lifecycle Hook: onStart

Called before the cycle detection starts for each compilation.

/**
 * Callback executed before cycle detection begins
 * @param {Object} info - Compilation information
 */
onStart: (info: CompilationInfo) => void;

Usage Example:

new CircularDependencyPlugin({
  onStart({ compilation }) {
    console.log('Starting circular dependency detection');
  }
})

Lifecycle Hook: onDetected

Called for each module that has a circular dependency detected. Allows complete override of default behavior.

/**
 * Callback executed when a circular dependency is detected
 * @param {Object} info - Detection information including module, paths, and compilation
 */
onDetected: (info: DetectionInfo) => void;

Usage Example:

new CircularDependencyPlugin({
  onDetected({ module, paths, compilation }) {
    // Custom handling - log the cycle
    console.log('Circular dependency detected:', paths.join(' -> '));
    
    // Add custom error/warning
    compilation.errors.push(new Error(`Custom: ${paths.join(' -> ')}`));
  }
})

Lifecycle Hook: onEnd

Called after the cycle detection ends for each compilation.

/**
 * Callback executed after cycle detection completes
 * @param {Object} info - Compilation information
 */
onEnd: (info: CompilationInfo) => void;

Usage Example:

// Count cycles and fail if exceeding limit
const MAX_CYCLES = 5;
let numCyclesDetected = 0;

new CircularDependencyPlugin({
  onStart({ compilation }) {
    numCyclesDetected = 0;
  },
  onDetected({ module, paths, compilation }) {
    numCyclesDetected++;
    compilation.warnings.push(new Error(paths.join(' -> ')));
  },
  onEnd({ compilation }) {
    if (numCyclesDetected > MAX_CYCLES) {
      compilation.errors.push(new Error(
        `Detected ${numCyclesDetected} cycles which exceeds limit of ${MAX_CYCLES}`
      ));
    }
  }
})

Plugin Apply Method

Standard webpack plugin interface method that registers the plugin with the compiler.

/**
 * Standard webpack plugin interface method
 * @param {Object} compiler - webpack compiler instance
 */
apply(compiler: any): void;

This method is called automatically by webpack when the plugin is registered and should not be called manually.

Cycle Detection Method

Internal method that performs the actual circular dependency detection algorithm.

/**
 * Recursively checks for circular dependencies in the module graph
 * @param {Object} initialModule - The module to check for cycles
 * @param {Object} currentModule - The current module being analyzed
 * @param {Object} seenModules - Cache of visited modules to avoid infinite recursion
 * @param {Object} compilation - webpack compilation object
 * @returns {Array|false} Array of module paths forming the cycle, or false if no cycle
 */
isCyclic(initialModule: any, currentModule: any, seenModules: object, compilation: any): string[] | false;

This method is used internally by the plugin and typically should not be called directly. It implements a depth-first search algorithm to traverse the dependency graph and detect cycles.

Configuration Options

Pattern Matching

Control which files are analyzed for circular dependencies:

new CircularDependencyPlugin({
  // Exclude node_modules and specific files
  exclude: /node_modules|\.spec\.js$/,
  
  // Only check files in src directory
  include: /src/,
})

Error Handling

Control how circular dependencies are reported:

new CircularDependencyPlugin({
  // Fail the build on circular dependencies
  failOnError: true,
  
  // Allow cycles that include async imports (e.g., dynamic imports with weak mode)
  // This prevents cycles involving import(/* webpackMode: "weak" */ './module') from being flagged
  allowAsyncCycles: true,
})

Path Display

Customize how module paths are displayed in error messages:

new CircularDependencyPlugin({
  // Use custom base directory for relative paths
  cwd: path.resolve(__dirname, 'src'),
})

Advanced Usage Patterns

Custom Cycle Detection Logic

new CircularDependencyPlugin({
  onDetected({ module, paths, compilation }) {
    // Skip certain types of cycles
    const isTestFile = paths.some(path => path.includes('.test.'));
    if (isTestFile) {
      return; // Skip test file cycles
    }

    // Custom error formatting
    const error = new Error(`Circular dependency: ${paths.join(' → ')}`);
    compilation.errors.push(error);
  }
})

Integration with Build Metrics

const buildMetrics = { cycleCount: 0, cycleFiles: new Set() };

new CircularDependencyPlugin({
  onDetected({ paths, compilation }) {
    buildMetrics.cycleCount++;
    paths.forEach(path => buildMetrics.cycleFiles.add(path));
  },
  onEnd({ compilation }) {
    console.log(`Build completed with ${buildMetrics.cycleCount} circular dependencies`);
    console.log(`Affected files: ${Array.from(buildMetrics.cycleFiles).join(', ')}`);
  }
})

Types

/**
 * Main plugin class for detecting circular dependencies
 */
class CircularDependencyPlugin {
  constructor(options?: PluginOptions);
  apply(compiler: any): void;
}

Notes

  • Circular dependencies are often legitimate in complex software architectures
  • The plugin helps identify potentially problematic cycles rather than all cycles
  • Compatible with both webpack 4 and webpack 5 with version-specific handling
  • Module resolution respects webpack's module graph for accurate detection
  • Self-referencing dependencies (CommonJsSelfReferenceDependency) are automatically ignored