Skip to content

Repository files navigation

vite-plugin-component-debugger

Vite Plugin Component Debugger

npm version npm downloads GitHub license GitHub stars

Build Status Auto Release TypeScript Vite

Buy Me A Coffee Follow on Twitter

A Vite plugin that adds data attributes to your JSX and TSX elements so you can tell which component rendered which piece of the DOM. Useful when you are debugging someone else's code, or code you generated and have not read yet.

What's new

v2.3.0 repairs a CommonJS entry that threw on require(), widens Vite support to 2 through 8, and fixes 19 defects found in an audit of v2.2.1. Full list in changelog.md.

Features

  • Path filtering with glob patterns, to include or exclude specific files
  • Attribute transformers to rewrite any attribute value, for privacy or formatting
  • Presets for common setups: minimal, testing, debugging, production
  • Conditional tagging through a shouldTag callback
  • Custom attributes of your own, such as git branch or environment
  • Metadata encoding as JSON, Base64, or plain text
  • Statistics and callbacks for tracking what was processed, with optional JSON export
  • Depth filtering to tag only certain nesting levels
  • Attribute grouping to collapse everything into one JSON attribute

Every option is opt-in. An existing config keeps working untouched.

Detailed examples and use cases

Compatibility

Vite Status Notes
8.x ✅ Supported Builds with Rolldown/oxc
7.x ✅ Supported Requires Node ^20.19.0 || >=22.12.0
6.x ✅ Supported
5.x ✅ Supported
4.x ✅ Supported
3.x ✅ Supported
2.x ✅ Supported

Node.js: >= 18.12.0 for the plugin itself. Vite 7 and 8 require Node 20.19+/22.12+, so your Vite version sets the real floor.

Every version in that table is verified by a real vite build in CI, not just declared in peerDependencies. Run it yourself with pnpm run vite-compat.

Quick Start

# Install
pnpm add -D vite-plugin-component-debugger
# or: npm install --save-dev vite-plugin-component-debugger
# or: yarn add -D vite-plugin-component-debugger
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import componentDebugger from "vite-plugin-component-debugger";
// A named import works too:
// import { componentDebugger } from "vite-plugin-component-debugger";

export default defineConfig({
  plugins: [
    componentDebugger({ // ⚠️ IMPORTANT: Must be BEFORE react()
      enabled: process.env.NODE_ENV === "development", // When to run
      attributePrefix: "data-dev", // Custom prefix
      extensions: [".jsx", ".tsx"], // File types
    }),
    react(),
  ],
});

⚠️ CRITICAL: componentDebugger() must be placed BEFORE react() in the plugins array, otherwise line numbers will be wrong.

Both this plugin and @vitejs/plugin-react's vite:react-babel declare enforce: 'pre'. Vite keeps the array order within the pre group, so listing componentDebugger first is what actually decides which transform runs first. The React plugin injects roughly 19 lines of imports and HMR setup, so running after it shifts every data-dev-line by that amount.

⚠️ enabled defaults to true, including production builds. This plugin has no apply restriction, so a bare componentDebugger() also tags your production bundle, embedding source paths and line numbers in the shipped DOM. Gate it explicitly:

componentDebugger({ enabled: process.env.NODE_ENV === "development" });

What It Does

Before:

// src/components/Button.tsx (line 10)
<button className="btn-primary" onClick={handleClick}>
  Click me
</button>

After (Default - All Attributes):

<button
  className="btn-primary"
  onClick={handleClick}
  data-dev-id="src/components/Button.tsx:10:2"
  data-dev-name="button"
  data-dev-path="src/components/Button.tsx"
  data-dev-line="10"
  data-dev-file="Button.tsx"
  data-dev-component="button"
>
  Click me
</button>

Attributes are appended after your existing props, immediately before the closing >.

After (Minimal Preset - Clean):

componentDebugger({ preset: 'minimal' })

// Results in:
<button
  className="btn-primary"
  onClick={handleClick}
  data-dev-id="src/components/Button.tsx:10:2"
>
  Click me
</button>

After (Custom Filtering):

componentDebugger({
  includeAttributes: ["id", "name", "line"]
})

// Results in:
<button
  data-dev-id="src/components/Button.tsx:10:2"
  data-dev-name="button"
  data-dev-line="10"
  className="btn-primary"
  onClick={handleClick}
>
  Click me
</button>

Why use it

  • Find which component rendered any DOM element, without guessing
  • Jump straight from DevTools to the source line
  • Select elements in E2E tests by a stable attribute instead of a brittle CSS path
  • Fragments and Three.js elements are skipped automatically, so your scene graph stays clean

The transform runs at build time and adds no runtime code. It does add attributes to your markup, though, so gate it with enabled if you do not want them in a production bundle.

Configuration

Basic Configuration

componentDebugger({
  enabled: process.env.NODE_ENV === "development", // When to run
  attributePrefix: "data-dev", // Custom prefix
  extensions: [".jsx", ".tsx"], // File types
});

Quick Start with Presets

// Minimal - only ID attribute (cleanest DOM)
componentDebugger({ preset: "minimal" });

// Testing - ID, name, component (perfect for E2E)
componentDebugger({ preset: "testing" });

// Debugging - everything + metadata (full visibility)
componentDebugger({ preset: "debugging" });

// Production - privacy-focused with shortened paths
componentDebugger({ preset: "production" });

See all preset details in EXAMPLES.md

Common Configurations

Clean DOM, minimal attributes
componentDebugger({
  includeAttributes: ["id", "name"], // Only these attributes
});
// Result: Only data-dev-id and data-dev-name

See more attribute filtering examples →

Path filtering for specific directories
componentDebugger({
  includePaths: ["src/components/**", "src/features/**"],
  excludePaths: ["**/*.test.tsx", "**/*.stories.tsx"],
});

See path filtering patterns →

Privacy: transform paths
componentDebugger({
  transformers: {
    path: (p) => p.split("/").slice(-2).join("/"), // Shorten paths
    id: (id) => id.split(":").slice(-2).join(":"), // Remove path from ID
  },
});

See transformer examples →

Conditional: tag specific components
componentDebugger({
  shouldTag: ({ elementName }) => {
    // Only tag custom components (uppercase)
    return elementName[0] === elementName[0].toUpperCase();
  },
});

See conditional tagging patterns →

Prefer includeAttributes over the legacy includeProps and includeContent. It produces a smaller DOM.

⚠️ Gotcha: When both includeAttributes and excludeAttributes are set, includeAttributes takes priority

Configuration Reference

Core Options
Option Type Default Description
enabled boolean true Enable/disable the plugin
attributePrefix string 'data-dev' Prefix for data attributes
extensions string[] ['.jsx', '.tsx'] File extensions to process
preset Preset undefined Quick config: 'minimal' | 'testing' | 'debugging' | 'production'
Attribute control
Option Type Default Description
includeAttributes AttributeName[] undefined Recommended: Only include these attributes
excludeAttributes AttributeName[] undefined Exclude these attributes
transformers object undefined Transform attribute values (privacy, formatting)
groupAttributes boolean false Combine all into single JSON attribute

Available: 'id', 'name', 'path', 'line', 'file', 'component', 'metadata'

→ Full attribute control examples

Path and element filtering
Option Type Default Description
includePaths string[] undefined Glob patterns to include
excludePaths string[] undefined Glob patterns to exclude
excludeElements string[] ['Fragment', 'React.Fragment'] Element names to skip
customExcludes Set<string> Three.js elements Custom elements to skip

→ Path filtering patterns

Conditional and custom
Option Type Default Description
shouldTag (info) => boolean undefined Conditionally tag components
customAttributes (info) => Record<string, string> undefined Add custom attributes dynamically
metadataEncoding MetadataEncoding 'json' Encoding: 'json' | 'base64' | 'none'

→ Conditional tagging→ Custom attributes

Depth, stats and advanced
Option Type Default Description
maxDepth number undefined Maximum nesting depth
minDepth number undefined Minimum nesting depth
tagOnlyRoots boolean false Only tag root elements
onTransform (stats) => void undefined Per-file callback
onComplete (stats) => void undefined Completion callback
exportStats string undefined Export stats to file
includeSourceMapHints boolean false Add a data-dev-sourcemap attribute (requires path)
debug boolean false Enable debug logging

→ Depth filtering→ Statistics

See complete TypeScript types: import { type TagOptions } from 'vite-plugin-component-debugger'

More examples in EXAMPLES.md

Examples include: E2E testing setups, debug overlays, monorepo configs, feature flags, performance monitoring, and more!

Use Cases

1. Development Debugging (Simple)

Find components in the DOM:

// In browser console
document.querySelectorAll('[data-dev-component="Button"]');
console.log("Button locations:", [...$$('[data-dev-path*="Button"]')]);

2. E2E Testing (Intermediate)

Stable selectors for tests:

// Cypress
cy.get('[data-dev-component="SubmitButton"]').click();
cy.get('[data-dev-path*="LoginForm"]').should("be.visible");

// Playwright
await page.click('[data-dev-component="SubmitButton"]');
await expect(page.locator('[data-dev-path*="LoginForm"]')).toBeVisible();

3. Visual Debugging Tools (Advanced)

Build custom debugging overlays:

// Show component boundaries on hover
document.addEventListener("mouseover", (e) => {
  const target = e.target;
  if (target.dataset?.devComponent) {
    target.style.outline = "2px solid red";
    console.log(`Component: ${target.dataset.devComponent}`);
    console.log(`Location: ${target.dataset.devPath}:${target.dataset.devLine}`);
  }
});

4. Performance Monitoring (Expert)

Track component render activity:

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.type === "childList") {
      mutation.addedNodes.forEach((node) => {
        if (node.dataset?.devId) {
          console.log(`Component rendered: ${node.dataset.devId}`);
        }
      });
    }
  });
});

observer.observe(document.body, { childList: true, subtree: true });

Advanced Features

Environment-Specific Setup

// Different configs per environment
const isDev = process.env.NODE_ENV === "development";
const isStaging = process.env.NODE_ENV === "staging";

export default defineConfig({
  plugins: [
    componentDebugger({
      enabled: isDev || isStaging,
      attributePrefix: isStaging ? "data-staging" : "data-dev",
      includeProps: isDev, // Enable metadata in development
      includeContent: isDev, // Enable content capture in development
    }),
    react(),
  ],
});

React Three Fiber Support

Automatically excludes Three.js elements:

// Default exclusions
componentDebugger({
  customExcludes: new Set([
    "mesh",
    "group",
    "scene",
    "camera",
    "ambientLight",
    "directionalLight",
    "pointLight",
    "boxGeometry",
    "sphereGeometry",
    "planeGeometry",
    "meshBasicMaterial",
    "meshStandardMaterial",
    // ... and many more
  ]),
});

// To include Three.js elements
componentDebugger({
  customExcludes: new Set(), // Empty set = tag everything
});

TypeScript Support

Full type definitions included:

import componentDebugger, { type TagOptions } from "vite-plugin-component-debugger";

const config: TagOptions = {
  enabled: true,
  attributePrefix: "data-track",
};

export default defineConfig({
  plugins: [componentDebugger(config), react()],
});

Build Performance & Statistics

Component Debugger Statistics:
   Total files scanned: 45
   Files processed: 32
   Elements tagged: 287

How it keeps out of the way:

  • Glob patterns are compiled once at startup rather than on every file
  • Metadata is serialized once per element instead of repeatedly
  • node_modules is skipped before any parsing happens
  • Files outside includePaths are rejected before the parser runs

v2.2.0 introduced these as optimizations over v2.1. The original release notes quoted specific speedups; those numbers are not reproduced here because there is no benchmark in the repo to back them up. If build time matters to you, measure it with onTransform on your own project.

Troubleshooting & Common Gotchas

⚠️ Line numbers are wrong/offset by ~19? (Most common issue)

Problem: data-dev-line shows numbers ~19 higher than expected

Cause: Plugin order is wrong - React plugin adds ~19 lines of imports/HMR setup

Fix: Move componentDebugger() BEFORE react() in Vite config

// ❌ WRONG - Line numbers will be offset
export default defineConfig({
  plugins: [
    react(), // Transforms code first, adds ~19 lines
    componentDebugger(), // Gets wrong line numbers
  ],
});

// ✅ CORRECT - Accurate line numbers
export default defineConfig({
  plugins: [
    componentDebugger(), // Processes original source first
    react(), // Transforms after tagging
  ],
});
Elements not being tagged?
  1. Check file extension: File must match extensions (default: .jsx, .tsx)
  2. Check exclusions: Element not in excludeElements or customExcludes
  3. Check paths: File not excluded by excludePaths pattern
  4. Check plugin order: componentDebugger() before react()
  5. Check enabled: Plugin is enabled (enabled: true)
  6. Check shouldTag: If using shouldTag, callback must return true

Debug with:

componentDebugger({
  debug: true, // Shows what's being processed
  enabled: true,
});
Build performance issues?

Quick fixes:

  1. Use includeAttributes to reduce DOM size:
    includeAttributes: ["id", "name"]; // Only essential attributes
  2. Filter paths to only process needed directories:
    includePaths: ['src/components/**'],
    excludePaths: ['**/*.test.tsx', '**/*.stories.tsx']
  3. Use maxDepth to limit deep nesting:
    maxDepth: 5; // Only tag up to 5 levels deep
  4. Skip test files with excludePaths

→ See performance optimization examples

Attributes appearing in production?
componentDebugger({
  enabled: process.env.NODE_ENV !== "production",
});

Or use environment-specific configs:

enabled: isDev || isStaging, // Not in production
includeAttributes vs excludeAttributes priority?

Gotcha: When both are set, includeAttributes takes priority

componentDebugger({
  includeAttributes: ["id", "name", "line"],
  excludeAttributes: ["name"], // ⚠️ This is IGNORED
});
// Result: Only id, name, line are included

Best practice: Use one or the other, not both

TypeScript type errors?

Import types for full IntelliSense:

import componentDebugger, {
  type TagOptions,
  type ComponentInfo,
  type AttributeName,
} from "vite-plugin-component-debugger";

const config: TagOptions = {
  // Full type checking
};

Development & Contributing

Auto-Release Workflow

Every commit to main triggers an automatic release:

Commit Message → Version Bump:

  • BREAKING CHANGE: or major: → Major (1.0.0 → 2.0.0)
  • feat: or feature: or minor: → Minor (1.0.0 → 1.1.0)
  • Everything else → Patch (1.0.0 → 1.0.1)

Example commit messages:

# Major version (breaking changes)
git commit -m "BREAKING CHANGE: removed deprecated API"
git commit -m "major: complete rewrite of plugin interface"

# Minor version (new features)
git commit -m "feat: add TypeScript 5.0 support"
git commit -m "feature: new configuration option for props"
git commit -m "minor: add custom exclude patterns"

# Patch version (bug fixes, docs, chores)
git commit -m "fix: resolve memory leak in transformer"
git commit -m "docs: update README examples"
git commit -m "chore: update dependencies"

# Skip release
git commit -m "docs: fix typo [skip ci]"

What happens automatically:

  1. Tests run, package builds
  2. Version bump based on commit message
  3. GitHub release created with changelog
  4. Package published to npm

Setup auto-publishing:

  1. Get NPM token: npm token create --type=automation
  2. Add to GitHub repo: SettingsSecretsNPM_TOKEN
  3. Commit to main branch to trigger first release

Contributing

  1. Fork and clone
  2. pnpm install
  3. Make changes and add tests
  4. pnpm run check (lint + test + build)
  5. Commit with semantic message (see above)
  6. Open PR

See .github/COMMIT_CONVENTION.md for examples.

Development Setup

git clone https://github.com/yourusername/vite-plugin-component-debugger.git
cd vite-plugin-component-debugger
pnpm install
pnpm run test     # Run tests
pnpm run build    # Build package
pnpm run check    # Full validation

Author & Support

Tonye Brown - Builder, Front-end developer, designer, and performance optimization expert crafting immersive web experiences. Also a Music Producer and Artist.

Connect:

Support This Project:

License

MIT © Tonye Brown


Made with ❤️ by Tonye Brown

Inspired by lovable-tagger, enhanced for the Vite ecosystem.

GitHub Website LinkedIn

Star this repo if it helped you!

Releases

Packages

Contributors

Languages