css-deadweigth
Version:
CLI tool to detect unused CSS in your project
107 lines (85 loc) ⢠3.52 kB
JavaScript
import { program } from 'commander';
import chalk from 'chalk';
import { globby } from 'globby';
import path from 'path';
import chokidar from 'chokidar';
import { extractSelectors } from '../lib/parseCss.js';
import { extractUsedSelectors } from '../lib/scanSource.js';
import { findUnusedSelectors } from '../lib/compare.js';
import { loadConfig } from '../lib/config.js';
import { printUnusedSelectors, saveReport } from '../lib/reporter.js';
import { isIgnored } from '../lib/utils.js';
async function runScan(options) {
const config = await loadConfig();
const ignoreSelectors = [...(config.ignoreSelectors || []), ...(options.ignore || [])];
const ignorePaths = (config.ignorePaths || []).map(p => `**/${p}/**`);
const srcPath = path.resolve(process.cwd(), options.src);
const cssPath = path.resolve(process.cwd(), options.css);
const sourceFiles = await globby([`${srcPath}/**/*.{html,js,jsx,ts,tsx}`], { ignore: ignorePaths });
const cssFiles = await globby([`${cssPath}/**/*.css`], { ignore: ignorePaths });
console.log(chalk.green(`š Scanning source: ${srcPath}`));
console.log(chalk.blue(`šØ Scanning CSS: ${cssPath}`));
console.log(chalk.yellow(`š¦ Found ${sourceFiles.length} source files.`));
console.log(chalk.yellow(`š
Found ${cssFiles.length} CSS files.`));
const allSelectors = await extractSelectors(cssFiles);
const usedSelectors = await extractUsedSelectors(sourceFiles);
let unused = findUnusedSelectors(allSelectors, usedSelectors);
unused = unused.filter(sel => !isIgnored(sel, ignoreSelectors));
printUnusedSelectors(unused, allSelectors.size, allSelectors.size - unused.length);
if (options.report) {
await saveReport(unused, allSelectors.size, allSelectors.size - unused.length, options.report);
}
if (unused.length > 0) {
console.log(chalk.red.bold('\nā Unused CSS selectors found!'));
if (!options.watch) process.exit(1);
} else {
console.log(chalk.green.bold('\nā
No unused CSS selectors found!'));
if (!options.watch) process.exit(0);
}
}
async function main() {
program
.version('1.0.0')
.description('Detect unused CSS in your project')
.requiredOption('--src <path>', 'Source folder')
.requiredOption('--css <path>', 'CSS folder')
.option('--report <file>', 'Save JSON report')
.option('--ignore <selectors...>', 'Ignore selectors (supports wildcards)')
.option('--watch', 'Watch mode: scan on file changes')
.parse(process.argv);
const options = program.opts();
if (options.watch) {
console.log(chalk.magenta('š Watch mode enabled. Watching for file changes...'));
const watcher = chokidar.watch([options.src, options.css], {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true,
});
const debouncedRun = debounce(() => runScan(options), 500);
watcher.on('add', path => {
console.log(chalk.cyan(`File added: ${path}`));
debouncedRun();
});
watcher.on('change', path => {
console.log(chalk.cyan(`File changed: ${path}`));
debouncedRun();
});
watcher.on('unlink', path => {
console.log(chalk.cyan(`File removed: ${path}`));
debouncedRun();
});
// Initial scan
await runScan(options);
} else {
await runScan(options);
}
}
// Simple debounce to avoid rapid repeated scans
function debounce(fn, delay) {
let timeoutId;
return () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(fn, delay);
};
}
main();