---
name: analyze-usage
description: Analyze TailwindCSS utility usage patterns, identify optimization opportunities, and generate usage reports
tools: Read, Bash, Grep, Glob, Write
---
# Analyze TailwindCSS Usage
This command analyzes your TailwindCSS usage patterns across your codebase to identify optimization opportunities, unused utilities, and usage statistics.
## What This Command Does
1. **Usage Pattern Analysis**
- Scans all template files for TailwindCSS class usage
- Identifies most and least used utility patterns
- Generates usage frequency reports
- Detects potential optimization opportunities
2. **Bundle Size Analysis**
- Analyzes generated CSS bundle size
- Identifies largest utility categories
- Compares before/after optimization results
- Tracks bundle size over time
3. **Code Quality Insights**
- Identifies overly complex utility combinations
- Suggests component extraction opportunities
- Detects inconsistent utility usage patterns
- Highlights potential refactoring opportunities
4. **Performance Recommendations**
- Suggests safelist optimizations
- Identifies unused CSS that can be purged
- Recommends content path improvements
- Provides bundle optimization suggestions
## Usage Examples
### Basic Usage Analysis
```bash
# Analyze utility usage in all template files
grep -r "class[Name]*=" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.html" | \
sed -E 's/.*class[Name]*=["'\''`]([^"'\''`]*)["'\''`].*/\1/' | \
tr ' ' '\n' | \
sort | uniq -c | sort -nr > tailwind-usage-report.txt
# View top 20 most used utilities
head -20 tailwind-usage-report.txt
# View least used utilities
tail -20 tailwind-usage-report.txt
```
### Advanced Pattern Analysis
```bash
# Find all TailwindCSS classes in codebase
find src -name "*.{js,jsx,ts,tsx,vue,html}" -exec grep -l "class" {} \; | \
xargs grep -o "class[Name]*=['\"][^'\"]*['\"]" | \
sed -E 's/.*class[Name]*=["'\''`]([^"'\''`]*)["'\''`].*/\1/' | \
tr ' ' '\n' | \
grep -E '^[a-zA-Z]' | \
sort | uniq -c | sort -nr
```
### Component Complexity Analysis
```bash
# Find components with many utility classes (potential extraction candidates)
find src/components -name "*.{jsx,tsx}" -exec sh -c '
for file do
count=$(grep -o "class[Name]*=['\"][^'\"]*['\"]" "$file" | \
sed -E "s/.*class[Name]*=[\"\'\`]([^\"\'\`]*)[\"\'\`].*/\1/" | \
tr " " "\n" | wc -l)
echo "$count $file"
done
' sh {} + | sort -nr | head -10
```
## Analysis Scripts
### Comprehensive Usage Analyzer
```javascript
// scripts/analyze-tailwind-usage.js
const fs = require('fs')
const path = require('path')
const glob = require('glob')
class TailwindUsageAnalyzer {
constructor(options = {}) {
this.srcPaths = options.srcPaths || ['src/**/*.{js,jsx,ts,tsx,vue,html}']
this.outputPath = options.outputPath || './tailwind-analysis.json'
this.classPattern = /(?:class|className)(?:Name)?[`:=]\s*[`"']([^`"']*)[`"']/g
}
async analyze() {
const files = this.getTemplateFiles()
const results = {
totalFiles: files.length,
totalClasses: 0,
utilityStats: {},
fileStats: {},
categoryStats: {},
complexityStats: {},
timestamp: new Date().toISOString()
}
for (const file of files) {
const content = fs.readFileSync(file, 'utf8')
const fileClasses = this.extractClasses(content)
results.fileStats[file] = {
classCount: fileClasses.length,
uniqueClasses: [...new Set(fileClasses)].length,
complexity: this.calculateComplexity(fileClasses)
}
// Update utility stats
fileClasses.forEach(className => {
results.utilityStats[className] = (results.utilityStats[className] || 0) + 1
results.totalClasses++
// Categorize utility
const category = this.categorizeUtility(className)
results.categoryStats[category] = (results.categoryStats[category] || 0) + 1
})
}
// Calculate additional insights
results.insights = this.generateInsights(results)
results.recommendations = this.generateRecommendations(results)
// Save results
fs.writeFileSync(this.outputPath, JSON.stringify(results, null, 2))
return results
}
getTemplateFiles() {
const files = []
this.srcPaths.forEach(pattern => {
files.push(...glob.sync(pattern))
})
return files
}
extractClasses(content) {
const classes = []
let match
while ((match = this.classPattern.exec(content)) !== null) {
const classString = match[1]
const classList = classString.split(/\s+/).filter(cls => cls.length > 0)
classes.push(...classList)
}
return classes
}
categorizeUtility(className) {
const categories = {
layout: /^(block|inline|flex|grid|table|hidden|container)/,
spacing: /^(p|m|space)[trblxy]?-/,
sizing: /^(w|h|max-w|max-h|min-w|min-h)-/,
typography: /^(text|font|leading|tracking|whitespace)/,
colors: /^(bg|text|border|ring)-.+-(50|100|200|300|400|500|600|700|800|900|950)$/,
borders: /^(border|rounded|ring|divide)/,
effects: /^(shadow|opacity|blur)/,
filters: /^(filter|backdrop|brightness|contrast|grayscale)/,
animation: /^(animate|transition|duration|ease|delay)/,
transforms: /^(transform|scale|rotate|translate|skew)/,
interactivity: /^(cursor|select|resize|outline|appearance)/,
responsive: /^(sm|md|lg|xl|2xl):/,
state: /^(hover|focus|active|disabled|group|peer):/,
}
for (const [category, pattern] of Object.entries(categories)) {
if (pattern.test(className)) {
return category
}
}
return 'other'
}
calculateComplexity(classes) {
const uniqueClasses = new Set(classes)
const responsiveClasses = classes.filter(c => /^(sm|md|lg|xl|2xl):/.test(c))
const stateClasses = classes.filter(c => /^(hover|focus|active|group|peer):/.test(c))
return {
total: classes.length,
unique: uniqueClasses.size,
responsive: responsiveClasses.length,
interactive: stateClasses.length,
ratio: classes.length / uniqueClasses.size
}
}
generateInsights(results) {
const sortedUtilities = Object.entries(results.utilityStats)
.sort(([,a], [,b]) => b - a)
const sortedCategories = Object.entries(results.categoryStats)
.sort(([,a], [,b]) => b - a)
const complexFiles = Object.entries(results.fileStats)
.sort(([,a], [,b]) => b.complexity.total - a.complexity.total)
.slice(0, 10)
return {
mostUsedUtilities: sortedUtilities.slice(0, 20),
leastUsedUtilities: sortedUtilities.slice(-20),
topCategories: sortedCategories,
mostComplexFiles: complexFiles,
averageClassesPerFile: results.totalClasses / results.totalFiles,
uniqueUtilityCount: Object.keys(results.utilityStats).length
}
}
generateRecommendations(results) {
const recommendations = []
// Check for overused utilities
const overusedUtilities = results.insights.mostUsedUtilities
.filter(([,count]) => count > results.totalFiles * 0.8)
if (overusedUtilities.length > 0) {
recommendations.push({
type: 'component-extraction',
message: 'Consider extracting components for frequently used utility combinations',
utilities: overusedUtilities.slice(0, 5).map(([name]) => name)
})
}
// Check for complex files
const complexFiles = results.insights.mostComplexFiles
.filter(([,stats]) => stats.complexity.total > 50)
if (complexFiles.length > 0) {
recommendations.push({
type: 'complexity-reduction',
message: 'These files have high utility complexity and may benefit from refactoring',
files: complexFiles.slice(0, 5).map(([file]) => file)
})
}
// Check for unused categories
const lowUsageCategories = Object.entries(results.categoryStats)
.filter(([,count]) => count < results.totalClasses * 0.01)
if (lowUsageCategories.length > 0) {
recommendations.push({
type: 'config-optimization',
message: 'Consider removing unused utility categories from your build',
categories: lowUsageCategories.map(([name]) => name)
})
}
return recommendations
}
}
// Usage
const analyzer = new TailwindUsageAnalyzer({
srcPaths: ['src/**/*.{jsx,tsx}', 'pages/**/*.{jsx,tsx}'],
outputPath: './reports/tailwind-usage.json'
})
analyzer.analyze().then(results => {
console.log('TailwindCSS Usage Analysis Complete!')
console.log(`Analyzed ${results.totalFiles} files`)
console.log(`Found ${results.totalClasses} utility class usages`)
console.log(`${results.insights.uniqueUtilityCount} unique utilities`)
console.log(`Average ${results.insights.averageClassesPerFile.toFixed(1)} classes per file`)
console.log('\nTop 10 Most Used Utilities:')
results.insights.mostUsedUtilities.slice(0, 10).forEach(([name, count]) => {
console.log(` ${name}: ${count} usages`)
})
console.log('\nRecommendations:')
results.recommendations.forEach(rec => {
console.log(` ${rec.type}: ${rec.message}`)
})
})
```
### Bundle Size Analyzer
```javascript
// scripts/analyze-bundle-size.js
const fs = require('fs')
const gzipSize = require('gzip-size')
const brotliSize = require('brotli-size')
async function analyzeBundleSize(cssFilePath) {
const css = fs.readFileSync(cssFilePath, 'utf8')
const originalSize = Buffer.byteLength(css, 'utf8')
const gzipped = await gzipSize(css)
const brotlied = await brotliSize(css)
// Extract utility classes
const utilities = css.match(/\.[a-zA-Z][a-zA-Z0-9_-]*(?::[\w-]+)*(?:,\s*\.[a-zA-Z][a-zA-Z0-9_-]*(?::[\w-]+)*)*\s*{[^}]+}/g) || []
// Categorize utilities
const categories = {
layout: 0, spacing: 0, typography: 0, colors: 0,
borders: 0, effects: 0, animations: 0, responsive: 0
}
let categorySize = { ...categories }
utilities.forEach(rule => {
const size = Buffer.byteLength(rule, 'utf8')
if (/\.(flex|grid|block|inline)/.test(rule)) {
categorySize.layout += size
} else if (/\.(p|m|space)-/.test(rule)) {
categorySize.spacing += size
} else if (/\.(text|font)-/.test(rule)) {
categorySize.typography += size
} else if (/\.(bg|text|border)-.+-(50|100|200|300|400|500|600|700|800|900)/.test(rule)) {
categorySize.colors += size
} else if (/\.(border|rounded|ring)/.test(rule)) {
categorySize.borders += size
} else if (/\.(shadow|opacity|blur)/.test(rule)) {
categorySize.effects += size
} else if (/\.(animate|transition)/.test(rule)) {
categorySize.animations += size
} else if (/@media/.test(rule)) {
categorySize.responsive += size
}
})
return {
original: originalSize,
gzipped,
brotlied,
utilityCount: utilities.length,
categoryBreakdown: categorySize,
compressionRatio: {
gzip: (originalSize / gzipped).toFixed(2),
brotli: (originalSize / brotlied).toFixed(2)
}
}
}
// Generate size report
async function generateSizeReport(cssPath) {
const analysis = await analyzeBundleSize(cssPath)
console.log('CSS Bundle Size Analysis')
console.log('========================')
console.log(`Original size: ${(analysis.original / 1024).toFixed(2)} KB`)
console.log(`Gzipped size: ${(analysis.gzipped / 1024).toFixed(2)} KB (${analysis.compressionRatio.gzip}x compression)`)
console.log(`Brotli size: ${(analysis.brotlied / 1024).toFixed(2)} KB (${analysis.compressionRatio.brotli}x compression)`)
console.log(`Utility rules: ${analysis.utilityCount}`)
console.log('\nSize by Category:')
Object.entries(analysis.categoryBreakdown)
.sort(([,a], [,b]) => b - a)
.forEach(([category, size]) => {
const percentage = ((size / analysis.original) * 100).toFixed(1)
console.log(` ${category}: ${(size / 1024).toFixed(2)} KB (${percentage}%)`)
})
}
// Usage: node scripts/analyze-bundle-size.js dist/styles.css
generateSizeReport(process.argv[2])
```
## Usage Reports
### HTML Report Generator
```javascript
// scripts/generate-usage-report.js
function generateHTMLReport(analysisData) {
const html = `
TailwindCSS Usage Report
TailwindCSS Usage Analysis Report
Generated on: ${analysisData.timestamp}
Overview
${analysisData.totalFiles} files analyzed
${analysisData.totalClasses} utility usages
${analysisData.insights.uniqueUtilityCount} unique utilities
${analysisData.insights.averageClassesPerFile.toFixed(1)} avg classes/file
Top Utility Categories
| Category | Usage Count | Percentage |
${analysisData.insights.topCategories.slice(0, 10).map(([cat, count]) => `
| ${cat} |
${count} |
${((count / analysisData.totalClasses) * 100).toFixed(1)}% |
`).join('')}
Most Used Utilities
| Utility | Usage Count | Files |
${analysisData.insights.mostUsedUtilities.slice(0, 20).map(([util, count]) => `
${util} |
${count} |
${Math.round((count / analysisData.totalFiles) * 100)}% |
`).join('')}
Most Complex Files
| File | Total Classes | Unique Classes | Complexity Ratio |
${analysisData.insights.mostComplexFiles.slice(0, 10).map(([file, stats]) => `
${file} |
${stats.complexity.total} |
${stats.complexity.unique} |
${stats.complexity.ratio.toFixed(2)} |
`).join('')}
Recommendations
${analysisData.recommendations.map(rec => `
-
${rec.type.replace('-', ' ')}: ${rec.message}
${rec.utilities ? `
Utilities: ${rec.utilities.join(', ')}` : ''}
${rec.files ? `
Files: ${rec.files.slice(0, 3).join(', ')}` : ''}
${rec.categories ? `
Categories: ${rec.categories.join(', ')}` : ''}
`).join('')}
`
fs.writeFileSync('./reports/tailwind-usage-report.html', html)
console.log('HTML report generated: ./reports/tailwind-usage-report.html')
}
```
## Automation and Monitoring
### CI/CD Integration
```yaml
# .github/workflows/tailwind-analysis.yml
name: TailwindCSS Usage Analysis
on:
pull_request:
paths:
- 'src/**/*.{js,jsx,ts,tsx}'
- 'tailwind.config.js'
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run TailwindCSS usage analysis
run: node scripts/analyze-tailwind-usage.js
- name: Generate size analysis
run: |
npm run build:css
node scripts/analyze-bundle-size.js dist/styles.css > bundle-size-report.txt
- name: Comment PR with analysis
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const analysis = JSON.parse(fs.readFileSync('./reports/tailwind-usage.json', 'utf8'));
const sizeReport = fs.readFileSync('bundle-size-report.txt', 'utf8');
const body = `## 📊 TailwindCSS Analysis
**Usage Statistics:**
- Files analyzed: ${analysis.totalFiles}
- Total utility usages: ${analysis.totalClasses}
- Unique utilities: ${analysis.insights.uniqueUtilityCount}
- Average classes per file: ${analysis.insights.averageClassesPerFile.toFixed(1)}
**Bundle Size:**
\`\`\`
${sizeReport}
\`\`\`
**Top Recommendations:**
${analysis.recommendations.slice(0, 3).map(rec => `- ${rec.message}`).join('\n')}
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
```
Remember: **Regular analysis helps maintain optimal TailwindCSS usage and identifies optimization opportunities early!**