qleaner 1.0.23 โ 1.0.25
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/README.md +95 -3
- package/command.js +12 -43
- package/controllers/image.js +13 -28
- package/package.json +3 -2
- package/utils/cssImages.js +5 -6
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ A powerful CLI tool to analyze and clean up your React codebase by finding unuse
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- ๐ **Scan for unused files**: Identify files that are not imported anywhere in your project
|
|
8
|
+
- ๐ผ๏ธ **Scan for unused images**: Find image files that are not referenced in your codebase
|
|
8
9
|
- ๐ **List all imports**: Get a complete list of all import statements in your codebase with file locations
|
|
9
10
|
- ๐ฏ **File listing**: List all files in your project
|
|
10
11
|
- ๐ **Table output**: Display results in formatted tables for better readability
|
|
@@ -13,6 +14,7 @@ A powerful CLI tool to analyze and clean up your React codebase by finding unuse
|
|
|
13
14
|
- ๐ช **TypeScript support**: Works with TypeScript, JavaScript, JSX, and TSX files
|
|
14
15
|
- ๐งช **Dry run mode**: Preview what would be deleted without actually deleting files
|
|
15
16
|
- ๐พ **Smart caching**: Caches scan results for faster subsequent runs (automatically invalidates on file changes)
|
|
17
|
+
- ๐จ **CSS and styled-components support**: Detects images used in CSS files and styled-components
|
|
16
18
|
|
|
17
19
|
## Installation
|
|
18
20
|
|
|
@@ -117,6 +119,63 @@ qleaner qlean-scan src --clear-cache
|
|
|
117
119
|
qleaner qlean-scan src --clear-cache --dry-run
|
|
118
120
|
```
|
|
119
121
|
|
|
122
|
+
### Scan for Unused Images
|
|
123
|
+
|
|
124
|
+
Find image files that are not referenced anywhere in your codebase:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
qleaner qlean-image <directory> <rootPath> [options]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Arguments:**
|
|
131
|
+
- `<directory>` - The path to the directory containing image files to scan
|
|
132
|
+
- `<rootPath>` - The root path to the project code that utilizes the images
|
|
133
|
+
|
|
134
|
+
**Options:**
|
|
135
|
+
- `-e, --exclude-dir-assets <dir...>` - Exclude directories from the asset scan
|
|
136
|
+
- `-f, --exclude-file-asset <file...>` - Exclude files from the asset scan
|
|
137
|
+
- `-F, --exclude-file-print-asset <files...>` - Scan but don't print the excluded asset files
|
|
138
|
+
- `-E, --exclude-dir-code <dir...>` - Exclude directories from the code scan
|
|
139
|
+
- `-S, --exclude-file-code <file...>` - Exclude files from the code scan
|
|
140
|
+
- `-P, --exclude-file-print-code <files...>` - Scan but don't print the excluded code files
|
|
141
|
+
- `-t, --table` - Display results in a formatted table
|
|
142
|
+
- `-d, --dry-run` - Show what would be deleted without actually deleting (skips prompt)
|
|
143
|
+
- `-C, --clear-cache` - Clear the cache before scanning
|
|
144
|
+
|
|
145
|
+
**Examples:**
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Scan for unused images in public/images directory
|
|
149
|
+
qleaner qlean-image public/images src
|
|
150
|
+
|
|
151
|
+
# Display unused images in a table format
|
|
152
|
+
qleaner qlean-image public/images src --table
|
|
153
|
+
|
|
154
|
+
# Scan with exclusions for assets
|
|
155
|
+
qleaner qlean-image public/images src -e public/images/icons
|
|
156
|
+
|
|
157
|
+
# Scan with exclusions for code files
|
|
158
|
+
qleaner qlean-image public/images src -E __tests__ node_modules
|
|
159
|
+
|
|
160
|
+
# Dry run - preview what would be deleted
|
|
161
|
+
qleaner qlean-image public/images src --dry-run
|
|
162
|
+
|
|
163
|
+
# Dry run with table output
|
|
164
|
+
qleaner qlean-image public/images src --dry-run --table
|
|
165
|
+
|
|
166
|
+
# Scan with multiple exclusions
|
|
167
|
+
qleaner qlean-image public/images src -e public/images/icons -E __tests__ -S "**/*.test.*"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**What it detects:**
|
|
171
|
+
- Images imported via `import` statements
|
|
172
|
+
- Images required via `require()` calls
|
|
173
|
+
- Images used in JSX `src` attributes
|
|
174
|
+
- Images in CSS `url()` functions (CSS and SCSS files)
|
|
175
|
+
- Images in styled-components and CSS-in-JS template literals
|
|
176
|
+
- Images in inline styles (backgroundImage, etc.)
|
|
177
|
+
- Images referenced in string literals and template literals
|
|
178
|
+
|
|
120
179
|
## Output Formats
|
|
121
180
|
|
|
122
181
|
Qleaner provides two output formats:
|
|
@@ -131,9 +190,12 @@ Qleaner provides two output formats:
|
|
|
131
190
|
- Import tables show: File, Line, Column, and Import path
|
|
132
191
|
- File tables show: File path
|
|
133
192
|
- Unused files table shows: Unused file paths (or "Would Delete" in dry run mode)
|
|
193
|
+
- Unused images table shows: Unused image paths (or "Would Delete" in dry run mode)
|
|
134
194
|
|
|
135
195
|
## How It Works
|
|
136
196
|
|
|
197
|
+
### File Scanning (qlean-scan)
|
|
198
|
+
|
|
137
199
|
1. **File Discovery**: Recursively finds all `.tsx`, `.ts`, `.js`, and `.jsx` files in the specified directory
|
|
138
200
|
2. **Caching**: Checks cache for previously scanned files. Files that haven't changed are skipped for faster performance
|
|
139
201
|
3. **Import Extraction**: Parses files using Babel AST and extracts all import statements (only for changed files or cache misses)
|
|
@@ -143,19 +205,49 @@ Qleaner provides two output formats:
|
|
|
143
205
|
7. **Reporting**: Outputs the results in standard or table format based on your preferences
|
|
144
206
|
8. **Safe Deletion**: In dry run mode, shows what would be deleted without making changes. In normal mode, prompts for confirmation before deletion
|
|
145
207
|
|
|
208
|
+
### Image Scanning (qlean-image)
|
|
209
|
+
|
|
210
|
+
1. **Image Discovery**: Recursively finds all image files (`.png`, `.jpg`, `.jpeg`, `.svg`, `.gif`, `.webp`) in the specified directory
|
|
211
|
+
2. **Code Scanning**: Scans all code files (`.js`, `.jsx`, `.ts`, `.tsx`) for image references
|
|
212
|
+
3. **Image Detection**: Detects images through multiple methods:
|
|
213
|
+
- Import statements (`import img from './image.png'`)
|
|
214
|
+
- Require calls (`require('./image.png')`)
|
|
215
|
+
- JSX src attributes (`<img src="./image.png" />`)
|
|
216
|
+
- CSS url() functions in CSS/SCSS files
|
|
217
|
+
- Styled-components and CSS-in-JS template literals
|
|
218
|
+
- Inline styles (backgroundImage, etc.)
|
|
219
|
+
- String and template literals containing image paths
|
|
220
|
+
4. **Path Normalization**: Normalizes all detected image paths for consistent matching
|
|
221
|
+
5. **Analysis**: Compares discovered image files with detected references to identify unused images
|
|
222
|
+
6. **Reporting**: Outputs the results in standard or table format
|
|
223
|
+
7. **Safe Deletion**: In dry run mode, shows what would be deleted without making changes. In normal mode, prompts for confirmation before deletion
|
|
224
|
+
|
|
146
225
|
## Supported File Types
|
|
147
226
|
|
|
227
|
+
**Code files:**
|
|
148
228
|
- `.js` - JavaScript files
|
|
149
229
|
- `.jsx` - JavaScript React files
|
|
150
230
|
- `.ts` - TypeScript files
|
|
151
231
|
- `.tsx` - TypeScript React files
|
|
152
232
|
|
|
233
|
+
**Image files (for qlean-image):**
|
|
234
|
+
- `.png` - PNG images
|
|
235
|
+
- `.jpg`, `.jpeg` - JPEG images
|
|
236
|
+
- `.svg` - SVG images
|
|
237
|
+
- `.gif` - GIF images
|
|
238
|
+
- `.webp` - WebP images
|
|
239
|
+
|
|
240
|
+
**CSS files (for image detection):**
|
|
241
|
+
- `.css` - CSS files
|
|
242
|
+
- `.scss` - SCSS files
|
|
243
|
+
|
|
153
244
|
## Use Cases
|
|
154
245
|
|
|
155
246
|
- ๐งน **Code cleanup**: Remove dead code and unused files from your React projects
|
|
247
|
+
- ๐ผ๏ธ **Image cleanup**: Find and remove unused image assets to reduce project size
|
|
156
248
|
- ๐ **Code analysis**: Understand import patterns and dependencies in your codebase
|
|
157
|
-
- ๐ **Project audit**: Identify orphaned files that may have been forgotten
|
|
158
|
-
- ๐ฆ **Bundle optimization**: Find files that can be removed to reduce bundle size
|
|
249
|
+
- ๐ **Project audit**: Identify orphaned files and assets that may have been forgotten
|
|
250
|
+
- ๐ฆ **Bundle optimization**: Find files and images that can be removed to reduce bundle size
|
|
159
251
|
- ๐ฏ **Maintenance**: Keep your codebase clean and maintainable
|
|
160
252
|
|
|
161
253
|
## Configuration
|
|
@@ -216,4 +308,4 @@ MIT
|
|
|
216
308
|
|
|
217
309
|
## Version
|
|
218
310
|
|
|
219
|
-
Current version: 1.0.
|
|
311
|
+
Current version: 1.0.25
|
package/command.js
CHANGED
|
@@ -4,7 +4,6 @@ const Table = require("cli-table3");
|
|
|
4
4
|
const parser = require("@babel/parser");
|
|
5
5
|
const traverse = require("@babel/traverse").default;
|
|
6
6
|
const path = require("path");
|
|
7
|
-
const cliProgress = require("cli-progress");
|
|
8
7
|
const { createResolver } = require("./utils/resolver");
|
|
9
8
|
const {
|
|
10
9
|
loadCache,
|
|
@@ -15,14 +14,6 @@ const {
|
|
|
15
14
|
const { isExcludedFile, compareFiles } = require("./utils/utils");
|
|
16
15
|
|
|
17
16
|
async function getFiles(directory = "src", options, chalk) {
|
|
18
|
-
const bars = new cliProgress.MultiBar({
|
|
19
|
-
clearOnComplete: false,
|
|
20
|
-
hideCursor: true,
|
|
21
|
-
format: chalk.cyan('{name}') +
|
|
22
|
-
' |' + '{bar}' + '| ' +
|
|
23
|
-
'{value}/{total} files || {status}'
|
|
24
|
-
}, cliProgress.Presets.shades_grey);
|
|
25
|
-
|
|
26
17
|
const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
|
|
27
18
|
if (options.excludeDir && options.excludeDir.length > 0) {
|
|
28
19
|
options.excludeDir.forEach((dir) => {
|
|
@@ -38,12 +29,11 @@ async function getFiles(directory = "src", options, chalk) {
|
|
|
38
29
|
const files = await fg(contentPaths);
|
|
39
30
|
const imports = [];
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
name: 'Scanning Files',
|
|
43
|
-
status: 'Scanning...',
|
|
44
|
-
});
|
|
45
|
-
|
|
32
|
+
let index = 0;
|
|
46
33
|
for (const file of files) {
|
|
34
|
+
index++;
|
|
35
|
+
console.clear();
|
|
36
|
+
console.log('Scanning file...', index, 'of', files.length);
|
|
47
37
|
const code = fs.readFileSync(file, "utf8");
|
|
48
38
|
const ast = parser.parse(code, {
|
|
49
39
|
sourceType: "module",
|
|
@@ -71,10 +61,7 @@ async function getFiles(directory = "src", options, chalk) {
|
|
|
71
61
|
// }
|
|
72
62
|
// },
|
|
73
63
|
// });
|
|
74
|
-
scanBar.increment();
|
|
75
64
|
}
|
|
76
|
-
|
|
77
|
-
bars.stop();
|
|
78
65
|
|
|
79
66
|
if (options.table) {
|
|
80
67
|
const tableImports = new Table({
|
|
@@ -125,14 +112,6 @@ async function getFiles(directory = "src", options, chalk) {
|
|
|
125
112
|
}
|
|
126
113
|
|
|
127
114
|
async function unUsedFiles(chalk, directory = "src", options) {
|
|
128
|
-
const bars = new cliProgress.MultiBar({
|
|
129
|
-
clearOnComplete: false,
|
|
130
|
-
hideCursor: true,
|
|
131
|
-
format: chalk.cyan('{name}') +
|
|
132
|
-
' |' + '{bar}' + '| ' +
|
|
133
|
-
'{value}/{total} files || {status}'
|
|
134
|
-
}, cliProgress.Presets.shades_grey);
|
|
135
|
-
|
|
136
115
|
const resolver = createResolver(directory);
|
|
137
116
|
const cache = loadCache(process.cwd());
|
|
138
117
|
const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
|
|
@@ -151,15 +130,13 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
151
130
|
let imports = [];
|
|
152
131
|
const unusedFiles = [];
|
|
153
132
|
|
|
154
|
-
const scanBar = bars.create(files.length, 0, {
|
|
155
|
-
name: 'Scanning Files',
|
|
156
|
-
status: 'Scanning...',
|
|
157
|
-
});
|
|
158
|
-
|
|
159
133
|
// debug log
|
|
160
134
|
// let debugCount = 0;
|
|
161
|
-
let
|
|
135
|
+
let index = 0;
|
|
162
136
|
for (const file of files) {
|
|
137
|
+
index++;
|
|
138
|
+
console.clear();
|
|
139
|
+
console.log('Checking file...', index, 'of', files.length);
|
|
163
140
|
const code = fs.readFileSync(file, "utf8");
|
|
164
141
|
if (needsRebuild(file, code, cache)) {
|
|
165
142
|
const ast = parser.parse(code, {
|
|
@@ -193,9 +170,6 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
193
170
|
// debugCount++;
|
|
194
171
|
// console.log('cache hit', debugCount);
|
|
195
172
|
}
|
|
196
|
-
scanBar.update(++i, {
|
|
197
|
-
status: 'Scanning...',
|
|
198
|
-
});
|
|
199
173
|
}
|
|
200
174
|
|
|
201
175
|
// imports beings empty shows all the files were cached
|
|
@@ -203,14 +177,13 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
203
177
|
imports = cache[directory]? cache[directory].imports: [];
|
|
204
178
|
}
|
|
205
179
|
|
|
206
|
-
const checkBar = bars.create(files.length, 0, {
|
|
207
|
-
name: 'Checking Usage',
|
|
208
|
-
status: 'Checking...',
|
|
209
|
-
});
|
|
210
180
|
|
|
211
181
|
// debugCount = 0;
|
|
212
|
-
|
|
182
|
+
index = 0;
|
|
213
183
|
for (const file of files) {
|
|
184
|
+
index++;
|
|
185
|
+
console.clear();
|
|
186
|
+
console.log('Checking file...', index, 'of', files.length);
|
|
214
187
|
const code = fs.readFileSync(file, "utf8");
|
|
215
188
|
if (!cache[file].isImported || needsRebuild(file, code, cache)) {
|
|
216
189
|
let i = 0;
|
|
@@ -240,12 +213,8 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
240
213
|
// debugCount++;
|
|
241
214
|
// console.log("debug hit", debugCount);
|
|
242
215
|
}
|
|
243
|
-
checkBar.update(++i, {
|
|
244
|
-
status: 'Checking...',
|
|
245
|
-
});
|
|
246
216
|
}
|
|
247
217
|
|
|
248
|
-
bars.stop();
|
|
249
218
|
cache[directory] = {
|
|
250
219
|
imports: imports,
|
|
251
220
|
}
|
package/controllers/image.js
CHANGED
|
@@ -6,7 +6,6 @@ const traverse = require("@babel/traverse").default;
|
|
|
6
6
|
const Table = require("cli-table3");
|
|
7
7
|
const { getCssImages } = require("../utils/cssImages");
|
|
8
8
|
const { askDeleteFiles } = require("../utils/utils");
|
|
9
|
-
const cliProgress = require("cli-progress");
|
|
10
9
|
|
|
11
10
|
// Regex for matching url("x.png") or url('x.png') or bg-[url('x.png')]
|
|
12
11
|
const URL_EXTRACT_REGEX = /url\((['"]?)([^"')]+)\1\)/gi;
|
|
@@ -108,14 +107,6 @@ function extractFromJSXStyle(node, file, collected) {
|
|
|
108
107
|
* MAIN FUNCTION
|
|
109
108
|
*/
|
|
110
109
|
async function getUnusedImages(chalk, imageDirectory, codeDirectory, options) {
|
|
111
|
-
const bars = new cliProgress.MultiBar({
|
|
112
|
-
clearOnComplete: false,
|
|
113
|
-
hideCursor: true,
|
|
114
|
-
format: chalk.cyan('{name}') +
|
|
115
|
-
' |' + '{bar}' + '| ' +
|
|
116
|
-
'{value}/{total} files || {status}'
|
|
117
|
-
}, cliProgress.Presets.shades_grey)
|
|
118
|
-
|
|
119
110
|
const used = new Set();
|
|
120
111
|
const unusedImages = [];
|
|
121
112
|
|
|
@@ -124,13 +115,11 @@ async function getUnusedImages(chalk, imageDirectory, codeDirectory, options) {
|
|
|
124
115
|
|
|
125
116
|
// ---- Scan Code Files ----
|
|
126
117
|
const codeFiles = await fg([`${codeDirectory}/**/*.{js,jsx,ts,tsx}`]);
|
|
127
|
-
|
|
128
|
-
name: 'Code Files',
|
|
129
|
-
status: 'Scanning...',
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
|
|
118
|
+
let index = 0;
|
|
133
119
|
for (const file of codeFiles) {
|
|
120
|
+
index++;
|
|
121
|
+
console.clear();
|
|
122
|
+
console.log('Scanning code files...', index, 'of', codeFiles.length);
|
|
134
123
|
const code = fs.readFileSync(file, "utf8");
|
|
135
124
|
|
|
136
125
|
const ast = parser.parse(code, {
|
|
@@ -253,41 +242,37 @@ async function getUnusedImages(chalk, imageDirectory, codeDirectory, options) {
|
|
|
253
242
|
extracted.forEach((v) => used.add(JSON.stringify({ path: v, file })));
|
|
254
243
|
},
|
|
255
244
|
});
|
|
256
|
-
codeBar.increment();
|
|
257
245
|
}
|
|
258
246
|
|
|
259
247
|
// Add CSS images
|
|
260
|
-
const cssImages = await getCssImages(codeDirectory
|
|
248
|
+
const cssImages = await getCssImages(codeDirectory);
|
|
261
249
|
cssImages.forEach((img) =>
|
|
262
250
|
used.add(JSON.stringify({ path: img.value, file: img.file }))
|
|
263
251
|
);
|
|
264
|
-
const normalizeBar = bars.create(used.size, 0, {
|
|
265
|
-
name: 'Normalizing Images',
|
|
266
|
-
status: 'Normalizing...',
|
|
267
|
-
});
|
|
268
252
|
|
|
269
253
|
// Normalize all used images
|
|
270
254
|
const normalizedUsed = new Set();
|
|
255
|
+
index = 0;
|
|
271
256
|
for (const entry of used) {
|
|
272
|
-
|
|
257
|
+
index++;
|
|
258
|
+
console.clear();
|
|
259
|
+
console.log('Normalizing images...', index, 'of', used.size);
|
|
273
260
|
const { path: p } = JSON.parse(entry);
|
|
274
261
|
const normalized = normalize(p, imageDirectory);
|
|
275
262
|
if (normalized) normalizedUsed.add(normalized);
|
|
276
263
|
}
|
|
277
264
|
|
|
278
|
-
const unusedBar = bars.create(imageFiles.length, 0, {
|
|
279
|
-
name: 'Determining Unused Images',
|
|
280
|
-
status: 'Determining...',
|
|
281
|
-
});
|
|
282
265
|
// ---- Determine unused ----
|
|
266
|
+
index = 0;
|
|
283
267
|
for (const img of imageFiles) {
|
|
284
|
-
|
|
268
|
+
index++;
|
|
269
|
+
console.clear();
|
|
270
|
+
console.log('Determining unused images...', index, 'of', imageFiles.length);
|
|
285
271
|
const full = path.resolve(img);
|
|
286
272
|
if (!normalizedUsed.has(full)) {
|
|
287
273
|
unusedImages.push(full);
|
|
288
274
|
}
|
|
289
275
|
}
|
|
290
|
-
bars.stop();
|
|
291
276
|
// ---- Output table or list ----
|
|
292
277
|
if (options.table) {
|
|
293
278
|
const table = new Table({
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qleaner",
|
|
3
3
|
"packageManager": "yarn@4.6.0",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.25",
|
|
5
5
|
"main": "command.js",
|
|
6
6
|
"bin": "./bin/cli.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"start": "node ./bin/cli.js",
|
|
9
|
-
"resolve": "node ./utils/resolver.js"
|
|
9
|
+
"resolve": "node ./utils/resolver.js",
|
|
10
|
+
"scan": "node ./scanning.js"
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {
|
|
12
13
|
"@types/node": "^20.10.5",
|
package/utils/cssImages.js
CHANGED
|
@@ -20,17 +20,16 @@ function extractCssImages(cssContent, images, file) {
|
|
|
20
20
|
return images
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
async function getCssImages(directory = "src"
|
|
23
|
+
async function getCssImages(directory = "src") {
|
|
24
24
|
const cssFiles = await fg([
|
|
25
25
|
`${directory}/**/*.{css,scss}`,
|
|
26
26
|
]);
|
|
27
27
|
let images = [];
|
|
28
|
-
|
|
29
|
-
name: 'CSS Files',
|
|
30
|
-
status: 'Scanning...',
|
|
31
|
-
});
|
|
28
|
+
let index = 0;
|
|
32
29
|
for (const file of cssFiles) {
|
|
33
|
-
|
|
30
|
+
index++;
|
|
31
|
+
console.clear();
|
|
32
|
+
console.log('Scanning CSS files...', index, 'of', cssFiles.length);
|
|
34
33
|
const css = fs.readFileSync(file, "utf-8");
|
|
35
34
|
images = extractCssImages(css, images, file);
|
|
36
35
|
}
|