npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rollup-plugin-circular-dependencies

v2.0.1

Published

Detect circular dependencies in Rollup projects

Downloads

11,939

Readme

rollup-plugin-circular-dependencies

Detect and report circular dependencies in your Rollup & Vite projects.

npm version npm bundle size npm downloads license


A Rollup/Vite plugin that analyses your module graph using Tarjan's algorithm and reports every circular dependency it finds. It replaces Rollup's built-in circular dependency warnings with richer, configurable output — including pretty-printed console logs, JSON file reports, per-cycle filtering, lifecycle hooks, and detection metrics.

Table of Contents

Installation

# npm
npm install -D rollup-plugin-circular-dependencies

# pnpm
pnpm add -D rollup-plugin-circular-dependencies

# yarn
yarn add -D rollup-plugin-circular-dependencies

Quick Start

// rollup.config.js
import { circularDependencies } from 'rollup-plugin-circular-dependencies';

export default {
  input: 'src/index.js',
  output: { dir: 'dist', format: 'esm' },
  plugins: [circularDependencies()],
};

If circular dependencies exist, the build will error by default. Set throwOnError: false to emit warnings instead.

API Reference

circularDependencies(options?)

Creates a Rollup plugin instance that detects circular dependencies in your project.

Signature:

function circularDependencies(options?: Options): Plugin;

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | options | Options | Plugin configuration. All fields are optional. |

Returns: Plugin — a Rollup plugin object.

Example:

import { circularDependencies } from 'rollup-plugin-circular-dependencies';

// Zero-config — errors on any cycle
circularDependencies();

// Warn instead of error
circularDependencies({ throwOnError: false });

DefaultFormatters

An object containing built-in output formatters.

DefaultFormatters.JSON()

Creates a JSON formatter that serializes cycle data with 2-space indentation.

Signature:

function JSON(): (data: CircularDependenciesData) => string;

Returns: A formatter function producing a JSON string.

Example:

import { circularDependencies, DefaultFormatters } from 'rollup-plugin-circular-dependencies';

circularDependencies({
  formatOut: DefaultFormatters.JSON(),
});

DefaultFormatters.Pretty(config?)

Creates a human-readable formatter with optional color support. Colors are enabled by default unless disabled via environment variables (NO_COLOR, NODE_DISABLE_COLORS, or FORCE_COLOR=0).

Signature:

function Pretty(config?: { colors?: boolean }): (data: CircularDependenciesData) => string;

Parameters:

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | config.colors | boolean | true (unless env-disabled) | Enable ANSI color output |

Returns: A formatter function producing a styled string.

Example:

import { circularDependencies, DefaultFormatters } from 'rollup-plugin-circular-dependencies';

// Disable colors explicitly
circularDependencies({
  formatOut: DefaultFormatters.Pretty({ colors: false }),
});

Options

Configuration object for the plugin. All fields are optional.

interface Options {
  enabled?: boolean;
  include?: FilterPattern;
  exclude?: FilterPattern;
  throwOnError?: boolean;
  outputFilePath?: string;
  debug?: boolean;
  formatOutModulePath?: (path: string) => string;
  formatOut?: (data: CircularDependenciesData) => unknown;
  ignoreCycle?: (cyclePaths: string[]) => boolean;
  onStart?: (pluginContext: PluginContext) => void;
  onDetected?: (modulePath: string, pluginContext: PluginContext) => void;
  onEnd?: (params: { rawOutput: CircularDependenciesData; formattedOutput: unknown; metrics: Metrics }, pluginContext: PluginContext) => void;
}

See the Configuration / Options table for detailed descriptions.


Metrics

Metrics collected during circular dependency detection. Provided in the onEnd callback.

interface Metrics {
  readonly modulesChecked: number;
  readonly cyclesFound: number;
  readonly largestCycleSize: number;
  readonly detectionTimeMs: number;
}

| Field | Type | Description | |-------|------|-------------| | modulesChecked | number | Total number of modules analyzed | | cyclesFound | number | Number of unique cycles detected | | largestCycleSize | number | Number of modules in the largest cycle | | detectionTimeMs | number | Detection time in milliseconds |


CircularDependenciesData

The raw data structure representing detected cycles, grouped by entry module.

type CircularDependenciesData = Record<string, string[][]>;

Each key is a module ID (file path). Each value is an array of cycles, where each cycle is an array of module IDs forming the circular chain.

Example value:

{
  "src/a.ts": [
    ["src/a.ts", "src/b.ts"],
    ["src/a.ts", "src/c.ts", "src/d.ts"]
  ]
}

Configuration / Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | enabled | boolean | true | Enable or disable the plugin entirely. | | include | FilterPattern | [/\.(c\|m)?[jt]s(x)?$/] | Glob or RegExp patterns for files to include. | | exclude | FilterPattern | [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/] | Glob or RegExp patterns for files to exclude. node_modules and .git are always excluded. | | throwOnError | boolean | true | true = build error on detected cycles. false = warning only. | | outputFilePath | string | "" | Path to write results to a file. Empty string = console output. | | debug | boolean | false | Enable verbose debug logging (module count, timing, cycle details). | | formatOutModulePath | (path: string) => string | path.relative(cwd, path) | Transform module paths in the output. | | formatOut | (data: CircularDependenciesData) => unknown | Pretty() (console) / JSON() (file) | Custom formatter for the output data. | | ignoreCycle | (cyclePaths: string[]) => boolean | () => false | Return true to ignore a specific cycle. Receives the array of module paths forming the cycle. | | onStart | (pluginContext: PluginContext) => void | () => {} | Called before cycle detection starts. | | onDetected | (modulePath: string, pluginContext: PluginContext) => void | () => {} | Called for each module that is part of a cycle. | | onEnd | (params, pluginContext: PluginContext) => void | () => {} | Called after detection completes. Receives { rawOutput, formattedOutput, metrics }. |

Note: FilterPattern is the type from @rollup/pluginutils — it accepts string | RegExp | Array<string | RegExp> | null.

Advanced Usage

Vite Integration

The plugin works with Vite out of the box:

// vite.config.ts
import { defineConfig } from 'vite';
import { circularDependencies } from 'rollup-plugin-circular-dependencies';

export default defineConfig({
  plugins: [
    circularDependencies({
      throwOnError: false,
    }),
  ],
});

Output to File

Write detection results to a file instead of the console. When outputFilePath is set, the default formatter automatically switches to DefaultFormatters.JSON().

import { circularDependencies } from 'rollup-plugin-circular-dependencies';

circularDependencies({
  outputFilePath: './reports/circular-deps.json',
  throwOnError: false,
});

To use the pretty formatter for file output instead:

import { circularDependencies, DefaultFormatters } from 'rollup-plugin-circular-dependencies';

circularDependencies({
  outputFilePath: './reports/circular-deps.txt',
  formatOut: DefaultFormatters.Pretty({ colors: false }),
});

Custom Formatters

Replace the built-in formatters with your own:

import { circularDependencies } from 'rollup-plugin-circular-dependencies';

circularDependencies({
  formatOut: (data) => {
    const allCycles = Object.values(data).flat();
    const summary = allCycles.map((cycle) => cycle.join(' → ')).join('\n');
    return `Found ${allCycles.length} cycle(s):\n${summary}`;
  },
});

Lifecycle Hooks

Use lifecycle hooks to integrate with your CI pipeline, logging, or monitoring:

import { circularDependencies } from 'rollup-plugin-circular-dependencies';

circularDependencies({
  throwOnError: false,

  onStart: (pluginContext) => {
    console.log('Scanning for circular dependencies...');
  },

  onDetected: (modulePath, pluginContext) => {
    console.log(`Cycle member: ${modulePath}`);
  },

  onEnd: ({ rawOutput, formattedOutput, metrics }, pluginContext) => {
    console.log(`Checked ${metrics.modulesChecked} modules`);
    console.log(`Found ${metrics.cyclesFound} cycle(s)`);
    console.log(`Largest cycle: ${metrics.largestCycleSize} modules`);
    console.log(`Detection took ${metrics.detectionTimeMs.toFixed(1)}ms`);

    // Example: fail CI if too many cycles
    if (metrics.cyclesFound > 10) {
      process.exit(1);
    }
  },
});

Ignoring Specific Cycles

Filter out known or acceptable cycles using the ignoreCycle callback:

import { circularDependencies } from 'rollup-plugin-circular-dependencies';

circularDependencies({
  ignoreCycle: (cyclePaths) => {
    // Ignore cycles in generated code
    if (cyclePaths.some((p) => p.includes('/generated/'))) {
      return true;
    }

    // Ignore a specific known cycle
    if (cyclePaths.some((p) => p.endsWith('store/index.ts'))) {
      return true;
    }

    return false;
  },
});

Custom Module Path Formatting

By default, output paths are relative to the current working directory. Customize this behavior with formatOutModulePath:

import { circularDependencies } from 'rollup-plugin-circular-dependencies';

circularDependencies({
  // Strip everything before 'src/'
  formatOutModulePath: (modulePath) => {
    const srcIndex = modulePath.indexOf('src/');
    return srcIndex >= 0 ? modulePath.slice(srcIndex) : modulePath;
  },
});

Debug Mode

Enable verbose logging to see detection details in the Rollup info channel:

circularDependencies({
  debug: true,
});

Output example:

[circular-dependencies] Checked 142 modules in 3.2ms. Found 2 cycle(s), largest cycle size: 3.
[circular-dependencies] Cycle: src/a.ts → src/a.ts → src/b.ts
[circular-dependencies] Cycle: src/c.ts → src/c.ts → src/d.ts → src/e.ts

Watch Mode

The plugin fully supports Rollup's watch mode. Internal state is automatically reset on each rebuild, ensuring accurate results without stale data or memory leaks.

// rollup.config.js
import { circularDependencies } from 'rollup-plugin-circular-dependencies';

export default {
  input: 'src/index.js',
  output: { dir: 'dist', format: 'esm' },
  watch: { clearScreen: false },
  plugins: [
    circularDependencies({
      throwOnError: false,
    }),
  ],
};
rollup -c --watch

See the watch-mode example for a complete demo.

Compatibility & Peer Dependencies

| Dependency | Version | Note | |------------|---------|------| | Node.js | >=20.12.0 | Required runtime | | rollup | ^3.0.0 \|\| ^4.0.0 | Peer dependency |

The plugin ships both ESM and CJS builds:

| Format | Entry | |--------|-------| | ESM | dist/index.mjs | | CJS | dist/index.cjs | | Types | dist/index.d.cts |

Runtime dependency: @rollup/pluginutils ^5.3.0 — used for include/exclude file filtering.

Troubleshooting / FAQ

The plugin reports no cycles, but I know they exist

Make sure your files match the default include pattern [/\.(c|m)?[jt]s(x)?$/]. If your files use non-standard extensions, configure the include option explicitly:

circularDependencies({
  include: [/\.vue$/, /\.[jt]sx?$/],
});

I see "No files to check" in the console

This means no modules passed the include/exclude filters. Check your patterns:

// Debug by logging which modules are processed
circularDependencies({
  debug: true,
  onDetected: (modulePath) => {
    console.log('Processing:', modulePath);
  },
});

I want warnings instead of errors

Set throwOnError to false:

circularDependencies({
  throwOnError: false,
});

Colors are not showing in my terminal

The plugin respects the NO_COLOR convention. Colors are disabled when any of these environment variables are set:

  • NO_COLOR=1 / NO_COLOR=true
  • NODE_DISABLE_COLORS=1 / NODE_DISABLE_COLORS=true
  • FORCE_COLOR=0 / FORCE_COLOR=false

You can also disable colors explicitly:

import { circularDependencies, DefaultFormatters } from 'rollup-plugin-circular-dependencies';

circularDependencies({
  formatOut: DefaultFormatters.Pretty({ colors: false }),
});

How does the plugin differ from Rollup's built-in circular dependency warnings?

Rollup emits individual CIRCULAR_DEPENDENCY warnings for each cycle it finds. This plugin:

  1. Suppresses Rollup's built-in warnings to avoid duplicates
  2. Groups cycles by entry module for clearer output
  3. Provides structured data (CircularDependenciesData) for programmatic use
  4. Adds filtering (ignoreCycle), lifecycle hooks, and metrics
  5. Supports file output for CI integration

Can I use this with Vite?

Yes. Vite uses Rollup under the hood for production builds, so the plugin works as-is. See Vite Integration.

Changelog

See CHANGELOG.md for a detailed history of all releases.

License

MIT © Aleksey Shelementev