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.
npm install circular-dependency-pluginconst CircularDependencyPlugin = require('circular-dependency-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/,
// 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(),
})
]
}The Circular Dependency Plugin integrates with webpack's plugin system through the standard webpack plugin interface. It operates during the compilation phase by:
compilation.hooks.optimizeModules phaseonStart, onDetected, onEnd) for custom integration with build processesThe 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.
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()
})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');
}
})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(' -> ')}`));
}
})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}`
));
}
}
})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.
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.
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/,
})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,
})Customize how module paths are displayed in error messages:
new CircularDependencyPlugin({
// Use custom base directory for relative paths
cwd: path.resolve(__dirname, 'src'),
})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);
}
})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(', ')}`);
}
})/**
* Main plugin class for detecting circular dependencies
*/
class CircularDependencyPlugin {
constructor(options?: PluginOptions);
apply(compiler: any): void;
}