Highly customizable Vite plugin that adds data attributes to JSX/TSX elements for development tracking, debugging, and testing. Features path filtering, transformers, presets, and more.
npm install vite-plugin-component-debuggershouldTag callback
bash
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
`
`typescript
// 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()
react(),
],
});
`
> β οΈ CRITICAL: componentDebugger() must be placed BEFORE react() plugin, otherwise line numbers will be wrong
What It Does
Before:
`jsx
// src/components/Button.tsx (line 10)
`
After (Default - All Attributes):
`jsx
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
`
After (Minimal Preset - Clean):
`jsx
componentDebugger({ preset: 'minimal' })
// Results in:
data-dev-id="src/components/Button.tsx:10:2"
className="btn-primary"
onClick={handleClick}
>
Click me
`
After (Custom Filtering):
`jsx
componentDebugger({
includeAttributes: ["id", "name", "line"]
})
// Results in:
data-dev-id="src/components/Button.tsx:10:2"
data-dev-name="button"
data-dev-line="10"
className="btn-primary"
onClick={handleClick}
>
Click me
`
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
$3
`typescript
componentDebugger({
enabled: process.env.NODE_ENV === "development", // When to run
attributePrefix: "data-dev", // Custom prefix
extensions: [".jsx", ".tsx"], // File types
});
`
$3
`typescript
// 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
$3
π― Clean DOM - Minimal Attributes
`typescript
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
`typescript
componentDebugger({
includePaths: ["src/components/", "src/features/"],
excludePaths: ["/.test.tsx", "/.stories.tsx"],
});
`
See path filtering patterns β
π§ Privacy - Transform Paths
`typescript
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
`typescript
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
$3
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 | 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 | 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
$3
Find components in the DOM:
`javascript
// In browser console
document.querySelectorAll('[data-dev-component="Button"]');
console.log("Button locations:", [...$$('[data-dev-path*="Button"]')]);
`
$3
Stable selectors for tests:
`javascript
// 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
Build custom debugging overlays:
`javascript
// 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});
}
});
`
$3
Track component render activity:
`javascript
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
$3
`typescript
// 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(),
],
});
`
$3
Automatically excludes Three.js elements:
`typescript
// 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
});
`
$3
Full type definitions included:
`typescript
import componentDebugger, { type TagOptions } from "vite-plugin-component-debugger";
const config: TagOptions = {
enabled: true,
attributePrefix: "data-track",
};
export default defineConfig({
plugins: [componentDebugger(config), react()],
});
`
$3
`
π 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
$3
β οΈ 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
`typescript
// β 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:
`typescript
componentDebugger({
debug: true, // Shows what's being processed
enabled: true,
});
`
Build performance issues?
Quick fixes:
1. Use includeAttributes to reduce DOM size:
`typescript
includeAttributes: ["id", "name"]; // Only essential attributes
`
2. Filter paths to only process needed directories:
`typescript
includePaths: ['src/components/**'],
excludePaths: ['/.test.tsx', '/.stories.tsx']
`
3. Use maxDepth to limit deep nesting:
`typescript
maxDepth: 5; // Only tag up to 5 levels deep
`
4. Skip test files with excludePaths
β See performance optimization examples
Attributes appearing in production?
`typescript
componentDebugger({
enabled: process.env.NODE_ENV !== "production",
});
`
Or use environment-specific configs:
`typescript
enabled: isDev || isStaging, // Not in production
`
includeAttributes vs excludeAttributes priority?
Gotcha: When both are set, includeAttributes takes priority
`typescript
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:
`typescript
import componentDebugger, {
type TagOptions,
type ComponentInfo,
type AttributeName,
} from "vite-plugin-component-debugger";
const config: TagOptions = {
// Full type checking
};
`
Development & Contributing
$3
π 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:
`bash
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
$3
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.
$3
`bash
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
``