Skip to content

Instantly share code, notes, and snippets.

@jerkovicl
Forked from eneajaho/analyze-components.js
Created March 25, 2025 23:08
Show Gist options
  • Save jerkovicl/86b2a035f18aaa02dd65f7738a5452b1 to your computer and use it in GitHub Desktop.
Save jerkovicl/86b2a035f18aaa02dd65f7738a5452b1 to your computer and use it in GitHub Desktop.
Check components on project if they are OnPush
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const ts = require('typescript'); // Requires 'npm install typescript'
// --- Configuration ---
const DEFAULT_PROJECT_PATH = '.'; // Default to current directory
const EXCLUDED_DIRS = ['node_modules', 'dist', '.angular', '.vscode', '.git']; // Directories to skip
const COMPONENT_FILE_SUFFIX = '.component.ts';
// --- End Configuration ---
function findAngularComponents(dir, allComponents, nonOnPushComponents) {
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip excluded directories
if (!EXCLUDED_DIRS.includes(entry.name)) {
findAngularComponents(fullPath, allComponents, nonOnPushComponents);
}
} else if (entry.isFile() && entry.name.endsWith(COMPONENT_FILE_SUFFIX)) {
analyzeComponentFile(fullPath, allComponents, nonOnPushComponents);
}
}
} catch (err) {
console.error(`Error reading directory ${dir}:`, err.message);
// Decide if you want to stop execution or just skip this directory
// process.exit(1);
}
}
function analyzeComponentFile(filePath, allComponents, nonOnPushComponents) {
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
const sourceFile = ts.createSourceFile(
filePath,
fileContent,
ts.ScriptTarget.Latest, // Use appropriate TS version target if needed
true // setParentNodes flag
);
let isComponent = false;
let usesOnPush = false;
ts.forEachChild(sourceFile, visitNode);
function visitNode(node) {
// Check if it's a class declaration
if (ts.isClassDeclaration(node)) {
const decorators = ts.getDecorators ? ts.getDecorators(node) : node.decorators; // Check for TS version compatibility
if (decorators) {
for (const decorator of decorators) {
// Check if the decorator is @Component
if (ts.isCallExpression(decorator.expression)) {
const decoratorName = decorator.expression.expression.getText(sourceFile);
if (decoratorName === 'Component') {
isComponent = true;
allComponents.push(filePath); // Add to total component list
// Get the argument object of the decorator
const arg = decorator.expression.arguments[0];
if (arg && ts.isObjectLiteralExpression(arg)) {
// Find the changeDetection property
for (const prop of arg.properties) {
if (ts.isPropertyAssignment(prop) && prop.name.getText(sourceFile) === 'changeDetection') {
// Check if the value is ChangeDetectionStrategy.OnPush
if (prop.initializer && ts.isPropertyAccessExpression(prop.initializer)) {
const strategyText = prop.initializer.getText(sourceFile);
if (strategyText === 'ChangeDetectionStrategy.OnPush') {
usesOnPush = true;
}
}
// Found the changeDetection property, no need to check further properties
break;
}
}
}
// Found the @Component decorator, no need to check other decorators on this class
break;
}
}
}
}
}
// Only continue traversal if we haven't confirmed it's an OnPush component
if (!isComponent || !usesOnPush) {
ts.forEachChild(node, visitNode);
}
}
// After checking the file, if it's a component but not OnPush, add it to the list
if (isComponent && !usesOnPush) {
nonOnPushComponents.push(filePath);
}
} catch (err) {
console.error(`Error analyzing file ${filePath}:`, err.message);
}
}
// --- Main Execution ---
const projectPath = path.resolve(process.argv[2] || DEFAULT_PROJECT_PATH);
console.log(`Analyzing Angular components in: ${projectPath}\n`);
if (!fs.existsSync(projectPath) || !fs.statSync(projectPath).isDirectory()) {
console.error(`Error: Project path "${projectPath}" does not exist or is not a directory.`);
process.exit(1);
}
const allComponentsList = [];
const nonOnPushComponentsList = [];
findAngularComponents(projectPath, allComponentsList, nonOnPushComponentsList);
// --- Report Results ---
console.log('--- Analysis Summary ---');
console.log(`Total Components Found: ${allComponentsList.length}`);
console.log(`Components NOT using OnPush: ${nonOnPushComponentsList.length}`);
console.log('------------------------\n');
if (nonOnPushComponentsList.length > 0) {
console.log('Components potentially needing ChangeDetectionStrategy.OnPush:');
nonOnPushComponentsList.forEach(filePath => {
// Make path relative to the initial project path for cleaner output
console.log(` - ${path.relative(projectPath, filePath)}`);
});
} else if (allComponentsList.length > 0) {
console.log('All identified components are using OnPush or change detection was not explicitly set (default).');
console.log('Note: This script checks for explicit "ChangeDetectionStrategy.OnPush". Components without an explicit strategy use the default (CheckAlways).')
} else {
console.log('No Angular component files (*.component.ts) found in the specified directory.');
}
console.log('\nAnalysis complete.');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment