@ui-entropy/scanner-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -0
- package/README.md +263 -0
- package/dist/analyze.d.ts +31 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +65 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +123 -0
- package/dist/crawl.d.ts +51 -0
- package/dist/crawl.d.ts.map +1 -0
- package/dist/crawl.js +153 -0
- package/dist/extract-usage.d.ts +38 -0
- package/dist/extract-usage.d.ts.map +1 -0
- package/dist/extract-usage.js +118 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/parse-css.d.ts +35 -0
- package/dist/parse-css.d.ts.map +1 -0
- package/dist/parse-css.js +186 -0
- package/dist/report.d.ts +55 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +137 -0
- package/dist/scan.d.ts +48 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +72 -0
- package/package.json +69 -0
package/dist/report.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { estimateWastedBytes } from './analyze.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate JSON report from analysis
|
|
4
|
+
*/
|
|
5
|
+
export function generateJsonReport(analysis, warnings = []) {
|
|
6
|
+
const totalSelectors = analysis.totalClasses + analysis.totalIds;
|
|
7
|
+
const usedSelectors = analysis.usedClasses + analysis.usedIds;
|
|
8
|
+
const unusedSelectors = analysis.unusedClasses.size + analysis.unusedIds.size;
|
|
9
|
+
return {
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
timestamp: new Date().toISOString(),
|
|
12
|
+
summary: {
|
|
13
|
+
totalCssBytes: analysis.totalCssBytes,
|
|
14
|
+
totalRules: analysis.totalRules,
|
|
15
|
+
totalSelectors,
|
|
16
|
+
usedSelectors,
|
|
17
|
+
unusedSelectors,
|
|
18
|
+
wastePercentage: analysis.wastePercentage,
|
|
19
|
+
estimatedWastedBytes: estimateWastedBytes(analysis),
|
|
20
|
+
filesScanned: analysis.filesScanned,
|
|
21
|
+
},
|
|
22
|
+
details: {
|
|
23
|
+
classes: {
|
|
24
|
+
total: analysis.totalClasses,
|
|
25
|
+
used: analysis.usedClasses,
|
|
26
|
+
unused: analysis.unusedClasses.size,
|
|
27
|
+
unusedList: Array.from(analysis.unusedClasses).sort(),
|
|
28
|
+
},
|
|
29
|
+
ids: {
|
|
30
|
+
total: analysis.totalIds,
|
|
31
|
+
used: analysis.usedIds,
|
|
32
|
+
unused: analysis.unusedIds.size,
|
|
33
|
+
unusedList: Array.from(analysis.unusedIds).sort(),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
warnings,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Generate human-readable text report
|
|
41
|
+
*/
|
|
42
|
+
export function generateTextReport(analysis) {
|
|
43
|
+
const wastedBytes = estimateWastedBytes(analysis);
|
|
44
|
+
const totalSelectors = analysis.totalClasses + analysis.totalIds;
|
|
45
|
+
const usedSelectors = analysis.usedClasses + analysis.usedIds;
|
|
46
|
+
let report = '';
|
|
47
|
+
report += '🔍 UI Entropy Analysis Report\n';
|
|
48
|
+
report += '═══════════════════════════════════════════════════════\n\n';
|
|
49
|
+
// Summary
|
|
50
|
+
report += '📊 Summary\n';
|
|
51
|
+
report += '─────────────────────────────────────────────────────\n';
|
|
52
|
+
report += `Total CSS Size: ${analysis.totalCssBytes.toLocaleString()} bytes\n`;
|
|
53
|
+
report += `Total Rules: ${analysis.totalRules.toLocaleString()}\n`;
|
|
54
|
+
report += `Files Scanned: ${analysis.filesScanned}\n\n`;
|
|
55
|
+
// Selectors
|
|
56
|
+
report += '🎯 Selectors\n';
|
|
57
|
+
report += '─────────────────────────────────────────────────────\n';
|
|
58
|
+
report += `Total Selectors: ${totalSelectors}\n`;
|
|
59
|
+
report += `Used Selectors: ${usedSelectors} (${Math.round((usedSelectors / totalSelectors) * 100)}%)\n`;
|
|
60
|
+
report += `Unused Selectors: ${totalSelectors - usedSelectors}\n\n`;
|
|
61
|
+
// Classes breakdown
|
|
62
|
+
report += `Classes: ${analysis.totalClasses} total\n`;
|
|
63
|
+
report += ` ├─ Used: ${analysis.usedClasses}\n`;
|
|
64
|
+
report += ` └─ Unused: ${analysis.unusedClasses.size}\n\n`;
|
|
65
|
+
// IDs breakdown
|
|
66
|
+
report += `IDs: ${analysis.totalIds} total\n`;
|
|
67
|
+
report += ` ├─ Used: ${analysis.usedIds}\n`;
|
|
68
|
+
report += ` └─ Unused: ${analysis.unusedIds.size}\n\n`;
|
|
69
|
+
// Waste calculation
|
|
70
|
+
report += '💰 CSS Waste Analysis\n';
|
|
71
|
+
report += '─────────────────────────────────────────────────────\n';
|
|
72
|
+
report += `Waste Percentage: ${analysis.wastePercentage}%\n`;
|
|
73
|
+
report += `Estimated Wasted Bytes: ${wastedBytes.toLocaleString()} bytes\n\n`;
|
|
74
|
+
// Top unused classes
|
|
75
|
+
if (analysis.unusedClasses.size > 0) {
|
|
76
|
+
report += '🚨 Top Unused Classes\n';
|
|
77
|
+
report += '─────────────────────────────────────────────────────\n';
|
|
78
|
+
const topUnused = Array.from(analysis.unusedClasses).slice(0, 20);
|
|
79
|
+
topUnused.forEach((cls, i) => {
|
|
80
|
+
report += ` ${i + 1}. .${cls}\n`;
|
|
81
|
+
});
|
|
82
|
+
if (analysis.unusedClasses.size > 20) {
|
|
83
|
+
report += ` ... and ${analysis.unusedClasses.size - 20} more\n`;
|
|
84
|
+
}
|
|
85
|
+
report += '\n';
|
|
86
|
+
}
|
|
87
|
+
// Unused IDs
|
|
88
|
+
if (analysis.unusedIds.size > 0) {
|
|
89
|
+
report += '🚨 Unused IDs\n';
|
|
90
|
+
report += '─────────────────────────────────────────────────────\n';
|
|
91
|
+
Array.from(analysis.unusedIds).forEach((id, i) => {
|
|
92
|
+
report += ` ${i + 1}. #${id}\n`;
|
|
93
|
+
});
|
|
94
|
+
report += '\n';
|
|
95
|
+
}
|
|
96
|
+
// Recommendations
|
|
97
|
+
report += '💡 Recommendations\n';
|
|
98
|
+
report += '─────────────────────────────────────────────────────\n';
|
|
99
|
+
if (analysis.wastePercentage > 50) {
|
|
100
|
+
report += ' ⚠️ HIGH WASTE: Consider using PurgeCSS or removing\n';
|
|
101
|
+
report += ' unused framework imports.\n\n';
|
|
102
|
+
}
|
|
103
|
+
else if (analysis.wastePercentage > 25) {
|
|
104
|
+
report += ' ⚡ MODERATE WASTE: Review and remove unused selectors.\n\n';
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
report += ' ✅ GOOD: CSS waste is within acceptable range.\n\n';
|
|
108
|
+
}
|
|
109
|
+
return report;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Generate a brief summary report (for CLI output)
|
|
113
|
+
*/
|
|
114
|
+
export function generateSummaryReport(analysis) {
|
|
115
|
+
const wastedBytes = estimateWastedBytes(analysis);
|
|
116
|
+
const totalSelectors = analysis.totalClasses + analysis.totalIds;
|
|
117
|
+
return `
|
|
118
|
+
CSS Waste: ${analysis.wastePercentage}% | ${wastedBytes.toLocaleString()} bytes wasted
|
|
119
|
+
Total: ${totalSelectors} selectors | Used: ${analysis.usedClasses + analysis.usedIds} | Unused: ${analysis.unusedClasses.size + analysis.unusedIds.size}
|
|
120
|
+
Scanned ${analysis.filesScanned} files | ${analysis.totalCssBytes.toLocaleString()} bytes CSS
|
|
121
|
+
`.trim();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Generate report in specified format
|
|
125
|
+
*/
|
|
126
|
+
export function generateReport(analysis, format = 'text', warnings = []) {
|
|
127
|
+
switch (format) {
|
|
128
|
+
case 'json':
|
|
129
|
+
return JSON.stringify(generateJsonReport(analysis, warnings), null, 2);
|
|
130
|
+
case 'summary':
|
|
131
|
+
return generateSummaryReport(analysis);
|
|
132
|
+
case 'text':
|
|
133
|
+
default:
|
|
134
|
+
return generateTextReport(analysis);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=report.js.map
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ParseWarning } from './parse-css.js';
|
|
2
|
+
import { type AnalysisResult } from './analyze.js';
|
|
3
|
+
import { type ReportFormat } from './report.js';
|
|
4
|
+
export interface ScanOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Directory to scan
|
|
7
|
+
* @default process.cwd()
|
|
8
|
+
*/
|
|
9
|
+
baseDir?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Report format
|
|
12
|
+
* @default 'text'
|
|
13
|
+
*/
|
|
14
|
+
format?: ReportFormat;
|
|
15
|
+
/**
|
|
16
|
+
* Override config file settings
|
|
17
|
+
*/
|
|
18
|
+
cssPatterns?: string[];
|
|
19
|
+
sourcePatterns?: string[];
|
|
20
|
+
ignorePatterns?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface ScanResult {
|
|
23
|
+
analysis: AnalysisResult;
|
|
24
|
+
report: string;
|
|
25
|
+
files: {
|
|
26
|
+
cssFiles: number;
|
|
27
|
+
sourceFiles: number;
|
|
28
|
+
totalBytes: number;
|
|
29
|
+
};
|
|
30
|
+
warnings: ParseWarning[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Scan a project directory for CSS waste
|
|
34
|
+
*
|
|
35
|
+
* This is the main entry point that orchestrates:
|
|
36
|
+
* 1. Load config
|
|
37
|
+
* 2. Crawl files
|
|
38
|
+
* 3. Extract CSS selectors
|
|
39
|
+
* 4. Extract usage from source files
|
|
40
|
+
* 5. Analyze waste
|
|
41
|
+
* 6. Generate report
|
|
42
|
+
*/
|
|
43
|
+
export declare function scan(options?: ScanOptions): ScanResult;
|
|
44
|
+
/**
|
|
45
|
+
* Quick scan that just returns the report as a string
|
|
46
|
+
*/
|
|
47
|
+
export declare function quickScan(baseDir?: string): string;
|
|
48
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,UAAU,CA+D1D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,MAAsB,GAAG,MAAM,CAGjE"}
|
package/dist/scan.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { crawlSync } from './crawl.js';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
import { extractSelectorsFromMultiple } from './parse-css.js';
|
|
4
|
+
import { extractUsageFromMultiple } from './extract-usage.js';
|
|
5
|
+
import { analyzeWaste } from './analyze.js';
|
|
6
|
+
import { generateReport } from './report.js';
|
|
7
|
+
/**
|
|
8
|
+
* Scan a project directory for CSS waste
|
|
9
|
+
*
|
|
10
|
+
* This is the main entry point that orchestrates:
|
|
11
|
+
* 1. Load config
|
|
12
|
+
* 2. Crawl files
|
|
13
|
+
* 3. Extract CSS selectors
|
|
14
|
+
* 4. Extract usage from source files
|
|
15
|
+
* 5. Analyze waste
|
|
16
|
+
* 6. Generate report
|
|
17
|
+
*/
|
|
18
|
+
export function scan(options = {}) {
|
|
19
|
+
const { baseDir = process.cwd(), format = 'text', cssPatterns, sourcePatterns, ignorePatterns, } = options;
|
|
20
|
+
// 1. Load configuration
|
|
21
|
+
const config = getConfig(baseDir);
|
|
22
|
+
// Override with CLI options if provided
|
|
23
|
+
const finalCssPatterns = cssPatterns ?? config.cssPatterns;
|
|
24
|
+
const finalSourcePatterns = sourcePatterns ?? config.sourcePatterns;
|
|
25
|
+
// Merge ignore patterns: CLI patterns are ADDED to config patterns, not replacing them
|
|
26
|
+
const finalIgnorePatterns = ignorePatterns
|
|
27
|
+
? [...config.ignorePatterns, ...ignorePatterns]
|
|
28
|
+
: config.ignorePatterns;
|
|
29
|
+
// 2. Crawl filesystem
|
|
30
|
+
const crawlResult = crawlSync({
|
|
31
|
+
baseDir,
|
|
32
|
+
cssPatterns: finalCssPatterns,
|
|
33
|
+
sourcePatterns: finalSourcePatterns,
|
|
34
|
+
ignorePatterns: finalIgnorePatterns,
|
|
35
|
+
respectGitignore: config.respectGitignore,
|
|
36
|
+
});
|
|
37
|
+
// Early exit if no files found
|
|
38
|
+
if (crawlResult.cssFiles.length === 0) {
|
|
39
|
+
throw new Error('No CSS files found. Check your cssPatterns configuration.');
|
|
40
|
+
}
|
|
41
|
+
if (crawlResult.sourceFiles.length === 0) {
|
|
42
|
+
throw new Error('No source files found. Check your sourcePatterns configuration.');
|
|
43
|
+
}
|
|
44
|
+
// 3. Extract CSS selectors (pass file objects for better error reporting)
|
|
45
|
+
const selectors = extractSelectorsFromMultiple(crawlResult.cssFiles);
|
|
46
|
+
// 4. Extract usage from source files
|
|
47
|
+
const sourceContents = crawlResult.sourceFiles.map(f => f.content);
|
|
48
|
+
const usage = extractUsageFromMultiple(sourceContents);
|
|
49
|
+
// 5. Analyze waste
|
|
50
|
+
const analysis = analyzeWaste(selectors, usage);
|
|
51
|
+
// 6. Generate report
|
|
52
|
+
const reportFormat = config.output?.format ?? format;
|
|
53
|
+
const report = generateReport(analysis, reportFormat, selectors.warnings);
|
|
54
|
+
return {
|
|
55
|
+
analysis,
|
|
56
|
+
report,
|
|
57
|
+
files: {
|
|
58
|
+
cssFiles: crawlResult.stats.totalCssFiles,
|
|
59
|
+
sourceFiles: crawlResult.stats.totalSourceFiles,
|
|
60
|
+
totalBytes: crawlResult.stats.totalCssBytes + crawlResult.stats.totalSourceBytes,
|
|
61
|
+
},
|
|
62
|
+
warnings: selectors.warnings,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Quick scan that just returns the report as a string
|
|
67
|
+
*/
|
|
68
|
+
export function quickScan(baseDir = process.cwd()) {
|
|
69
|
+
const result = scan({ baseDir });
|
|
70
|
+
return result.report;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=scan.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ui-entropy/scanner-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CSS waste detection engine - finds unused classes and IDs in your codebase",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"type-check": "tsc --noEmit",
|
|
19
|
+
"demo": "tsx src/demo.ts",
|
|
20
|
+
"scan-demo": "tsx src/scan-demo.ts",
|
|
21
|
+
"test-real": "tsx src/test-real-repo.ts",
|
|
22
|
+
"test-tailwind": "tsx src/test-tailwind.ts"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"css",
|
|
26
|
+
"unused",
|
|
27
|
+
"waste",
|
|
28
|
+
"optimization",
|
|
29
|
+
"bundle-size",
|
|
30
|
+
"tailwind",
|
|
31
|
+
"postcss"
|
|
32
|
+
],
|
|
33
|
+
"author": "UI Entropy",
|
|
34
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
35
|
+
"homepage": "https://uientropy.com",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/ui-entropy/ui-entropy.git",
|
|
39
|
+
"directory": "packages/scanner-core"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/ui-entropy/ui-entropy/issues"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist/**/*.js",
|
|
46
|
+
"dist/**/*.d.ts",
|
|
47
|
+
"dist/**/*.d.ts.map",
|
|
48
|
+
"!dist/demo.*",
|
|
49
|
+
"!dist/scan-demo.*",
|
|
50
|
+
"!dist/test-*.* ",
|
|
51
|
+
"!dist/quick-test.*",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"packageManager": "pnpm@10.30.0",
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^25.2.3",
|
|
58
|
+
"tsx": "^4.21.0",
|
|
59
|
+
"typescript": "^5.9.3",
|
|
60
|
+
"vitest": "^4.0.18"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"fast-glob": "^3.3.3",
|
|
64
|
+
"ignore": "^7.0.5",
|
|
65
|
+
"postcss": "^8.5.6",
|
|
66
|
+
"postcss-selector-parser": "^7.1.1",
|
|
67
|
+
"zod": "^4.3.6"
|
|
68
|
+
}
|
|
69
|
+
}
|