This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Highly customizable Vite plugin that automatically adds data attributes to JSX/TSX elements during development for component debugging and tracking. Features path filtering, attribute transformers, presets, conditional tagging, and more.
Current Version: v2.0.0 (major feature release)
- Use
pnpmfor all operations (not npm or yarn)
pnpm run build- Build plugin using tsuppnpm run dev- Build in watch modepnpm run test- Run tests with vitest (exits automatically)pnpm run test:coverage- Run tests with coverage (exits automatically)pnpm run lint- Lint TypeScript filespnpm run check- Full validation (lint + test + build)pnpm run pre-publish- Pre-publish validation script
- Every commit to
maintriggers automatic release - Version bumping based on commit message:
BREAKING CHANGE:ormajor:→ Major version (1.0.0 → 2.0.0)feat:orfeature:orminor:→ Minor version (1.0.0 → 1.1.0)- All other commits → Patch version (1.0.0 → 1.0.1)
- Automatically: runs tests, builds, creates GitHub release, publishes to npm
- Skip releases: Add
[skip ci]to commit message
Example commit messages:
feat: add Vue.js support→ Minor version bumpfix: handle JSX fragments better→ Patch version bumpBREAKING CHANGE: drop Node 14→ Major version bumpdocs: improve README [skip ci]→ No release
Setup auto-publishing:
npm token create --type=automation- Add
NPM_TOKENsecret to GitHub repo settings - Commit to
mainto trigger first release
See .github/COMMIT_CONVENTION.md for detailed examples
Node.js: >= 18.12.0 (required for pnpm compatibility) pnpm: >= 9.x (set in workflows)
Version files: .nvmrc, .node-version specify Node 18.12.0
src/index.ts- Main entry point and type exportssrc/plugin.ts- Core Vite plugin implementation (~200 lines)src/types.ts- All TypeScript type definitionssrc/constants.ts- Three.js elements and presetssrc/utils.ts- Preset application, base64 encoding, path sanitizationsrc/helpers/path-matching.ts- Pre-compiled glob pattern matching (5-10x faster)src/helpers/attribute-generator.ts- Attribute generation pipelinesrc/helpers/ast-walker.ts- AST traversal and element taggingtsup.config.ts- Build configuration
- Intercepts Vite's transform hook for
.jsx/.tsxfiles - Pre-compiled glob patterns filter files (5-10x faster than runtime matching)
- Parses with Babel parser → walks AST with estree-walker
- Adds data attributes using magic-string (optimized single-pass JSON.stringify)
- Preserves source maps and build performance
@babel/parser- JSX/TSX parsingestree-walker- AST traversalmagic-string- Code modificationminimatch- Glob pattern matching for path filtering (v2)vite- Peer dependency
CRITICAL: The componentDebugger plugin MUST be placed BEFORE the React plugin:
// ✅ CORRECT - Plugin processes original source code
export default defineConfig({
plugins: [
componentDebugger({
enabled: process.env.NODE_ENV === 'development'
}),
react() // React plugin runs after componentDebugger
]
})
// ❌ WRONG - Plugin gets transformed code with wrong line numbers
export default defineConfig({
plugins: [
react(), // Adds ~19 lines of imports/HMR setup
componentDebugger() // Gets wrong line numbers (+19 offset)
]
})Why this matters: React plugin injects ~19 lines of imports and HMR code. If componentDebugger runs after React, line numbers will be offset by ~19 lines, causing data-dev-line attributes to be incorrect.
enabled- Enable/disable (default:true)extensions- File types (default:['.jsx', '.tsx'])attributePrefix- Data attribute prefix (default:'data-dev')preset- Quick config:'minimal','testing','debugging','production'(default:undefined)
includeAttributes- RECOMMENDED Allowlist of attributes (e.g.,['id', 'name']) - cleaner DOM, better performanceexcludeAttributes- Disallowlist of attributes (e.g.,['metadata', 'file'])transformers- Transform any attribute value for privacy/formatting (see below)groupAttributes- Combine all attributes into single JSON attribute (default:false)
Available attribute names: 'id', 'name', 'path', 'line', 'file', 'component', 'metadata'
Priority: When both includeAttributes and excludeAttributes are specified, includeAttributes takes priority.
includePaths- Glob patterns for files to include (e.g.,['src/components/**', 'src/features/**'])excludePaths- Glob patterns for files to exclude (e.g.,['**/*.test.tsx', '**/*.stories.tsx'])excludeElements- Element names to skip (default:['Fragment', 'React.Fragment'])customExcludes- Custom element exclusions as Set (default: Three.js elements)
shouldTag- Callback to conditionally tag components:(info: ComponentInfo) => booleancustomAttributes- Add custom data attributes:(info: ComponentInfo) => Record<string, string>
ComponentInfo interface:
interface ComponentInfo {
elementName: string;
filePath: string;
line: number;
column: number;
props?: Record<string, any>;
content?: string;
}metadataEncoding- Encoding format:'json'(default),'base64', or'none'includeProps- LEGACY Capture props in metadata (default:false) - useincludeAttributesinsteadincludeContent- LEGACY Capture content in metadata (default:false) - useincludeAttributesinstead
maxDepth- Maximum nesting depth to tag (e.g.,3= only tag up to 3 levels deep)minDepth- Minimum nesting depth to tagtagOnlyRoots- Only tag root-level elements (default:false)
onTransform- Callback after each file:(stats: TransformStats) => voidonComplete- Callback after all files:(stats: CompletionStats) => voidexportStats- File path to export statistics JSON (e.g.,'build-stats.json')
includeSourceMapHints- Add source map comments for debugging (default:false)debug- Enable debug logging (default:false)
Quick configurations for common use cases:
minimal- Only ID attribute (smallest footprint)testing- ID, name, component (perfect for E2E tests)debugging- Everything + props + content (full visibility)production- Privacy-focused with shortened paths
Customize any attribute value for privacy, formatting, or anonymization:
transformers: {
path: (p) => p.split('/').slice(-2).join('/'), // Shorten paths
id: (id) => id.split(':').slice(-2).join(':'), // Remove path from ID
name: (name) => name.toUpperCase(), // Custom formatting
line: (line) => `L${line}`, // Add prefix
file: (file) => 'REDACTED', // Anonymize
component: (comp) => `<${comp}>` // Custom format
}Use glob patterns with minimatch to include/exclude specific files:
includePaths: ['src/components/**', 'src/features/**'],
excludePaths: ['**/*.test.tsx', '**/*.stories.tsx', '**/__tests__/**']Tag only specific components using shouldTag callback:
shouldTag: ({ elementName, filePath, props }) => {
// Only tag custom components (uppercase)
if (elementName[0] === elementName[0].toUpperCase()) return true;
// Only tag components with data-testid
if (props && 'data-testid' in props) return true;
// Only tag components in features directory
if (filePath.includes('features/')) return true;
return false;
}Add your own data attributes dynamically:
customAttributes: ({ elementName, filePath }) => ({
'data-dev-env': process.env.NODE_ENV,
'data-dev-branch': execSync('git branch --show-current').toString().trim(),
'data-dev-category': filePath.includes('features/') ? 'feature' : 'component'
})- Uses
depthStackarray to track JSX nesting during AST traversal - Increments on
enterfor JSX opening elements, decrements onleave - Enables
maxDepth,minDepth, andtagOnlyRootsfiltering
- Collect base attributes (id, name, path, line, file, component)
- Apply
includeAttributes/excludeAttributesfiltering - Apply
transformersto selected attributes - Optionally group into single JSON attribute with
groupAttributes - Add custom attributes from
customAttributescallback - Serialize and inject into code with magic-string
- Tracks files processed, elements tagged, and breakdown by element type
- Callbacks fired at transform (per-file) and buildEnd (completion)
- Optional JSON export with
exportStats
- #1: Single JSON.stringify for metadata - Reuses serialization result instead of calling 3 times (2-3x faster metadata encoding)
- #2: Pre-compiled glob patterns - Compiles minimatch patterns once at init instead of every file check (5-10x faster path matching)
- #3: Single string split for debug logging - Splits code once and reuses for first/last line display (2x faster debug mode)
- All v2 features are opt-in with
undefineddefaults for backwards compatibility - Attribute filtering reduces DOM size
includeAttributesrecommended over legacyincludeProps/includeContent- Overall improvement: 15-30% faster for typical use cases (save 200-500ms on 100-file projects, 2-5s on 1000-file projects)
- Make changes in
src/ - Run:
pnpm run check(lint + test + build) - Commit with semantic message for auto-release
- Dual ESM/CJS builds via tsup
- TypeScript declarations included
- Source maps for debugging
- Output:
dist/
Symptoms: data-dev-line attributes show line numbers ~19 higher than expected
Cause: Plugin is running after React plugin
Solution: Move componentDebugger() BEFORE react() in Vite config
Check:
- Plugin order (componentDebugger before react)
- File extensions match (default:
.jsx,.tsx) - Plugin is enabled (
enabled: true) - File is not in
node_modules
Enable debug logging:
componentDebugger({
debug: true, // Shows processed code and line numbers
enabled: true
})Use Playwright or similar to verify DOM data-dev-line attributes match source:
// Test that div on source line 9 has data-dev-line="9"
const element = page.getByTestId('my-div');
const lineNumber = await element.getAttribute('data-dev-line');
expect(lineNumber).toBe('9');