component-prune
Version:
Detect and optionally remove unused UI components in modern frontend frameworks(React, Svelte, Solid)
69 lines (60 loc) • 1.64 kB
text/typescript
import fg from "fast-glob";
import path from "path";
import type { ComponentFile } from "./types";
const DEFAULT_EXTENSIONS = [
"ts",
"tsx",
"js",
"jsx",
"svelte",
"vue",
"solid.js",
];
const EXCLUDE_DIRS = [
"node_modules",
".next",
"dist",
"build",
".vercel",
".output",
];
export async function scanComponents(
rootDir: string,
additionalIgnorePatterns: string[] = []
): Promise<ComponentFile[]> {
const pattern = `**/*.{${DEFAULT_EXTENSIONS.join(",")}}`;
const ignore = [
...EXCLUDE_DIRS.map((d) => `${d}/**`),
...additionalIgnorePatterns,
];
const entries = await fg(pattern, {
cwd: rootDir,
ignore,
absolute: true,
});
const components = entries
.filter((file) => {
// const isComponent = isLikelyComponent(file);
// console.log(`File: ${file}, Is Component: ${isComponent}`);
return isLikelyComponent(file);
})
.map((filePath) => ({
filePath,
name: path.basename(filePath, path.extname(filePath)),
extension: path.extname(filePath),
}));
return components;
}
// this function uses regex to detect diff naming convention of components
// --kebab, camel, pascal, snake, etc
// --all components inside components/ui/ dir is treated as component regardless of naming convention
function isLikelyComponent(filePath: string): boolean {
const filename = path.basename(filePath);
const isInUiDir = filePath.includes(path.join("components", "ui"));
return (
isInUiDir || // Include all files in components/ui
/^[A-Za-z][A-Za-z0-9_-]*\.(tsx|jsx|js|ts|svelte|vue|solid\.js)$/.test(
filename
)
);
}