const fs = require('fs');
const path = require('path');
const glob = require('glob');
const IMAGE_EXTENSIONS = ['.svg', '.png'];
const SOURCE_FILE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.html', '.css', '.less'];
const isDryRun = process.argv.includes('--dry-run');
const projectRoot = process.cwd();
const allImageFiles = new Set();
const referencedImages = new Set();
function normalizePath(filePath) {
return filePath.split(path.sep).join('/');
}
function findAllImageFiles() {
for (const ext of IMAGE_EXTENSIONS) {
const files = glob.sync(**/*${ext}, {
cwd: projectRoot,
nodir: true,
ignore: ['node_modules/', 'dist/', 'build/**'],
});
for (const file of files) {
allImageFiles.add(normalizePath(path.resolve(projectRoot, file)));
}
}
}
function extractImagePaths(content) {
const result = [];
// 匹配 url(...) 样式
const urlRegex = /url((['"]?)([^"')]+?.(svg|png))\1)/g;
let match;
while ((match = urlRegex.exec(content)) !== null) {
result.push(match[2]); // match[2] 是路径
}
// import ... from '...svg|png'
const importRegex = /import\s+.*?'"['"]/g;
while ((match = importRegex.exec(content)) !== null) {
result.push(match[1]);
}
// src="...svg|png"
const srcRegex = /src='"['"]/g;
while ((match = srcRegex.exec(content)) !== null) {
result.push(match[1]);
}
return result;
}
function findReferencedImages() {
const sourceFiles = glob.sync(
**/*.{${SOURCE_FILE_EXTENSIONS.map((e) => e.slice(1)).join(',')}},
{
cwd: projectRoot,
nodir: true,
ignore: ['node_modules/', 'dist/', 'build/**'],
},
);
for (const relativeFilePath of sourceFiles) {
const absFilePath = path.resolve(projectRoot, relativeFilePath);
const dir = path.dirname(absFilePath);
let content;
try {
content = fs.readFileSync(absFilePath, 'utf-8');
} catch {
continue;
}
const imagePaths = extractImagePaths(content);
for (const imgPath of imagePaths) {
const resolvedPath = normalizePath(path.resolve(dir, imgPath));
if (allImageFiles.has(resolvedPath)) {
referencedImages.add(resolvedPath);
}
}
}
}
function deleteUnreferencedImages() {
let deletedCount = 0;
for (const imgPath of allImageFiles) {
if (!referencedImages.has(imgPath)) {
const relativePath = path.relative(projectRoot, imgPath);
if (isDryRun) {
console.log([dry-run] 🚫 Will delete: ${relativePath});
} else {
fs.unlinkSync(imgPath);
console.log(🗑️ Deleted: ${relativePath});
}
deletedCount++;
}
}
console.log(\n✅ Total ${isDryRun ? 'would be deleted' : 'deleted'}: ${deletedCount} image(s).);
}
function main() {
console.log(🔍 Scanning for unused images${isDryRun ? ' (dry-run mode)' : ''}...\n);
findAllImageFiles();
findReferencedImages();
deleteUnreferencedImages();
}
main();
// node xxxx.js --dry-run。模拟删除
// node xx.js 真实删除