Skip to content

Instantly share code, notes, and snippets.

@frankhale
Created August 8, 2025 12:44
Show Gist options
  • Save frankhale/543f53dbf0508d60f2b448a407620cef to your computer and use it in GitHub Desktop.
Save frankhale/543f53dbf0508d60f2b448a407620cef to your computer and use it in GitHub Desktop.
Finds unused CSS classes in Angular components and SCSS files
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Function to extract CSS class names from SCSS file
function extractCssClasses(scssContent) {
const classRegex = /\.([a-zA-Z][a-zA-Z0-9_-]*)\s*\{/g;
const classes = new Set();
let match;
while ((match = classRegex.exec(scssContent)) !== null) {
classes.add(match[1]);
}
return Array.from(classes);
}
// Function to extract used classes from HTML file
function extractUsedClasses(htmlContent) {
const usedClasses = new Set();
// Extract classes from standard class attributes
const classRegex = /class\s*=\s*["']([^"']*)["']/g;
let match;
while ((match = classRegex.exec(htmlContent)) !== null) {
const classes = match[1].split(/\s+/);
classes.forEach(cls => {
if (cls.trim()) {
usedClasses.add(cls.trim());
}
});
}
// Extract classes from Angular property binding syntax [class.className]
const angularClassRegex = /\[class\.([a-zA-Z][a-zA-Z0-9_-]*)\]/g;
while ((match = angularClassRegex.exec(htmlContent)) !== null) {
usedClasses.add(match[1]);
}
return Array.from(usedClasses);
}
// Function to analyze a component
function analyzeComponent(componentPath) {
// Get actual file names
const scssFiles = fs.readdirSync(componentPath).filter(f => f.endsWith('.component.scss'));
const htmlFiles = fs.readdirSync(componentPath).filter(f => f.endsWith('.component.html'));
if (scssFiles.length === 0 || htmlFiles.length === 0) {
return null;
}
const scssContent = fs.readFileSync(path.join(componentPath, scssFiles[0]), 'utf8');
const htmlContent = fs.readFileSync(path.join(componentPath, htmlFiles[0]), 'utf8');
const definedClasses = extractCssClasses(scssContent);
const usedClasses = extractUsedClasses(htmlContent);
const unusedClasses = definedClasses.filter(cls => !usedClasses.includes(cls));
return {
component: path.basename(componentPath),
scssFile: scssFiles[0],
htmlFile: htmlFiles[0],
definedClasses,
usedClasses,
unusedClasses
};
}
// Main function to scan all components
function scanAllComponents() {
const srcPath = path.join(__dirname, 'src/app');
const results = [];
function scanDirectory(dir) {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
// Check if this directory contains component files
const hasComponentFiles = fs.readdirSync(fullPath).some(f =>
f.endsWith('.component.scss') || f.endsWith('.component.html')
);
if (hasComponentFiles) {
const result = analyzeComponent(fullPath);
if (result) {
results.push(result);
}
}
// Recursively scan subdirectories
scanDirectory(fullPath);
}
}
}
scanDirectory(srcPath);
return results;
}
// Run the analysis
const allResults = scanAllComponents();
const unusedCssResults = allResults.filter(result => result.unusedClasses.length > 0);
console.log('=== UNUSED CSS CLASSES ANALYSIS ===\n');
if (unusedCssResults.length === 0) {
console.log('✅ No unused CSS classes found!');
} else {
console.log(`Found ${unusedCssResults.length} components with unused CSS classes:\n`);
unusedCssResults.forEach(result => {
console.log(`📁 ${result.component}:`);
console.log(` Unused classes (${result.unusedClasses.length}):`);
result.unusedClasses.forEach(cls => console.log(` - ${cls}`));
console.log(` Total defined: ${result.definedClasses.length}, Total used: ${result.usedClasses.length}`);
console.log('');
});
// Summary
const totalUnused = unusedCssResults.reduce((sum, result) => sum + result.unusedClasses.length, 0);
const totalDefined = allResults.reduce((sum, result) => sum + result.definedClasses.length, 0);
const totalUsed = allResults.reduce((sum, result) => sum + result.usedClasses.length, 0);
console.log('=== SUMMARY ===');
console.log(`Total components analyzed: ${allResults.length}`);
console.log(`Components with unused classes: ${unusedCssResults.length}`);
console.log(`Total unused classes: ${totalUnused}`);
console.log(`Total defined classes: ${totalDefined}`);
console.log(`Total used classes: ${totalUsed}`);
console.log(`Usage rate: ${((totalUsed / totalDefined) * 100).toFixed(1)}%`);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment