Skip to content

canadianeagle/vite-plugin-component-debugger

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 highly customizable Vite plugin that automatically adds data attributes to JSX/TSX elements during development. Track, debug, and understand component rendering with powerful features like path filtering, attribute transformers, presets, and more. Perfect for AI-generated code and debugging "which component rendered this?" πŸ€”

✨ What's New in v2.2

Performance Optimizations (v2.2.0):

  • πŸš€ 15-30% faster build times with 3 micro-optimizations
  • ⚑ 5-10x faster path matching with pre-compiled glob patterns
  • πŸ“¦ 2-3x faster metadata encoding with optimized JSON serialization
  • πŸ”§ Modular architecture - Clean, maintainable 7-file structure

V2 Features - Complete control over component debugging:

  • 🎯 Path Filtering - Include/exclude files with glob patterns
  • πŸ”§ Attribute Transformers - Customize any attribute value (privacy, formatting)
  • 🎨 Presets - Quick configs for common use cases (minimal, testing, debugging, production)
  • ⚑ Conditional Tagging - Tag only specific components with shouldTag callback
  • 🏷️ Custom Attributes - Add your own data attributes (git info, environment, etc.)
  • πŸ“¦ Metadata Encoding - Choose JSON, Base64, or plain text encoding
  • πŸ“Š Statistics & Callbacks - Track processing stats and export metrics
  • 🎚️ Depth Filtering - Control tagging by component nesting level
  • πŸ” Attribute Grouping - Combine all attributes into single JSON attribute
  • πŸ—ΊοΈ Source Map Hints - Better debugging with source map comments

πŸ“š View Detailed Examples & Use Cases

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";

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() plugin, otherwise line numbers will be wrong

What It Does

Before:

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

After (Default - All Attributes):

<button
  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"
  className="btn-primary"
  onClick={handleClick}
>
  Click me
</button>

After (Minimal Preset - Clean):

componentDebugger({ preset: 'minimal' })

// Results in:
<button
  data-dev-id="src/components/Button.tsx:10:2"
  className="btn-primary"
  onClick={handleClick}
>
  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>

Key Benefits

  • πŸ› Debug Faster: Find which component renders any DOM element
  • πŸ“ Jump to Source: Go directly from DevTools to your code
  • 🎯 Stable Testing: Use data attributes for reliable E2E tests
  • ⚑ Zero Runtime Cost: Only runs during development
  • πŸ”§ Smart Exclusions: Automatically skips Fragment and Three.js elements

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 - 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 β†’

πŸ’‘ Pro Tip: Use includeAttributes for cleaner DOM instead of legacy includeProps/includeContent

⚠️ 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'
V2 Features - 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

V2 Features - Path & 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

V2 Features - Conditional & 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

V2 Features - Depth, Stats & 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 source map comments
debug boolean false Enable debug logging

β†’ Depth filtering β€’ β†’ Statistics

πŸ’‘ All v2 features are opt-in - Existing configs work unchanged

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

πŸ“š View 50+ Detailed 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

Performance optimizations (v2.2.0):

  • πŸš€ 15-30% faster than v2.1 with 3 micro-optimizations
  • ⚑ Pre-compiled glob patterns - 5-10x faster path matching
  • πŸ“¦ Optimized JSON serialization - 2-3x faster metadata encoding
  • πŸ”§ Smart string operations - 2x faster debug logging
  • Time savings: 200-500ms on 100-file projects, 2-5s on 1000-file projects
  • Efficient AST traversal with caching
  • Minimal HMR impact
  • Automatically skips node_modules
  • Only runs during development

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 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: Settings β†’ Secrets β†’ NPM_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:

  • ⭐ Star this repository
  • β˜• Buy me a coffee
  • πŸ’ Sponsor on GitHub
  • πŸ› Report issues or suggest features
  • 🀝 Contribute code via pull requests
  • πŸ“’ Share with other developers

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!