home-page/analyze-unused-images.js
2025-06-17 14:08:21 +08:00

254 lines
8.2 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const glob = require('glob');
/**
* 分析未使用的图片资源
* 扫描 src/blogs/ 目录下的 .assets 文件夹中的图片文件
* 检查这些图片是否在对应的 markdown 文件中被引用
*/
// 支持的图片格式
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg', '.webp'];
// 博客目录
const blogsDir = './src/blogs';
// 获取所有assets目录中的图片文件
function getAllAssetImages() {
const assetImages = [];
try {
// 使用 glob 匹配所有 .assets 目录下的文件
const assetFiles = glob.sync(`${blogsDir}/**/*.assets/**/*`, { nodir: true });
assetFiles.forEach(filePath => {
const ext = path.extname(filePath).toLowerCase();
if (imageExtensions.includes(ext)) {
assetImages.push({
fullPath: filePath,
fileName: path.basename(filePath),
blogName: path.dirname(filePath).split('/').pop().replace('.assets', ''),
relativePath: path.relative(blogsDir, filePath)
});
}
});
} catch (error) {
console.error('读取assets目录时出错:', error.message);
}
return assetImages;
}
// 获取所有markdown文件
function getAllMarkdownFiles() {
const markdownFiles = [];
try {
const mdFiles = glob.sync(`${blogsDir}/*.md`);
mdFiles.forEach(filePath => {
markdownFiles.push({
fullPath: filePath,
fileName: path.basename(filePath),
blogName: path.basename(filePath, '.md')
});
});
} catch (error) {
console.error('读取markdown文件时出错:', error.message);
}
return markdownFiles;
}
// 读取markdown文件内容并提取图片引用
function extractImageReferences(mdFilePath) {
try {
const content = fs.readFileSync(mdFilePath, 'utf8');
const imageRefs = new Set();
// 匹配 ![alt](path) 格式的图片引用
const markdownImageRegex = /!\[[^\]]*\]\(([^)]+)\)/g;
let match;
while ((match = markdownImageRegex.exec(content)) !== null) {
imageRefs.add(match[1]);
}
// 匹配 HTML img 标签
const htmlImageRegex = /<img[^>]+src=['"]*([^'">\s]+)['"]*[^>]*>/g;
while ((match = htmlImageRegex.exec(content)) !== null) {
imageRefs.add(match[1]);
}
return Array.from(imageRefs);
} catch (error) {
console.error(`读取文件 ${mdFilePath} 时出错:`, error.message);
return [];
}
}
// 检查图片是否被使用
function isImageUsed(imagePath, imageFileName, blogName, allMarkdownFiles) {
// 首先检查对应的markdown文件
const correspondingMdFile = allMarkdownFiles.find(md => md.blogName === blogName);
if (correspondingMdFile) {
const imageRefs = extractImageReferences(correspondingMdFile.fullPath);
// 检查是否有任何引用匹配当前图片
const isUsed = imageRefs.some(ref => {
// 处理相对路径和绝对路径
const normalizedRef = ref.replace(/^\.\//, '');
const fileName = path.basename(ref);
return (
fileName === imageFileName ||
normalizedRef.includes(imageFileName) ||
ref.includes(imageFileName) ||
normalizedRef === imagePath ||
ref === imagePath
);
});
if (isUsed) {
return { used: true, foundIn: correspondingMdFile.fileName };
}
}
// 如果在对应的markdown文件中没找到检查所有其他markdown文件
for (const mdFile of allMarkdownFiles) {
if (mdFile.blogName === blogName) continue; // 已经检查过了
const imageRefs = extractImageReferences(mdFile.fullPath);
const isUsed = imageRefs.some(ref => {
const normalizedRef = ref.replace(/^\.\//, '');
const fileName = path.basename(ref);
return (
fileName === imageFileName ||
normalizedRef.includes(imageFileName) ||
ref.includes(imageFileName)
);
});
if (isUsed) {
return { used: true, foundIn: mdFile.fileName };
}
}
return { used: false, foundIn: null };
}
// 主函数
function analyzeUnusedImages() {
console.log('🔍 开始分析未使用的图片资源...\n');
const assetImages = getAllAssetImages();
const markdownFiles = getAllMarkdownFiles();
console.log(`📊 统计信息:`);
console.log(` - 找到 ${assetImages.length} 个图片文件`);
console.log(` - 找到 ${markdownFiles.length} 个markdown文件\n`);
const unusedImages = [];
const usedImages = [];
console.log('📝 分析结果:\n');
assetImages.forEach(image => {
const usage = isImageUsed(image.relativePath, image.fileName, image.blogName, markdownFiles);
if (usage.used) {
usedImages.push({
...image,
foundIn: usage.foundIn
});
console.log(`${image.fileName} (在 ${usage.foundIn} 中使用)`);
} else {
unusedImages.push(image);
console.log(`${image.fileName} (未使用)`);
}
});
// 输出汇总
console.log('\n📋 汇总报告:');
console.log(` ✅ 已使用的图片: ${usedImages.length}`);
console.log(` ❌ 未使用的图片: ${unusedImages.length}`);
if (unusedImages.length > 0) {
console.log('\n🗑 未使用的图片列表:');
unusedImages.forEach(image => {
console.log(` - ${image.relativePath}`);
});
// 计算未使用图片的总大小
let totalSize = 0;
unusedImages.forEach(image => {
try {
const stats = fs.statSync(image.fullPath);
totalSize += stats.size;
} catch (error) {
console.error(`无法获取文件大小: ${image.fullPath}`);
}
});
console.log(`\n💾 未使用图片总大小: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
// 生成删除脚本包含git移除操作
const deleteScript = unusedImages.map(image => {
return `# 删除文件: ${image.relativePath}
if [ -f "${image.fullPath}" ]; then
# 检查文件是否被git跟踪
if git ls-files --error-unmatch "${image.fullPath}" >/dev/null 2>&1; then
echo "从git中移除: ${image.relativePath}"
git rm "${image.fullPath}"
else
echo "删除未跟踪文件: ${image.relativePath}"
rm "${image.fullPath}"
fi
else
echo "文件不存在,跳过: ${image.relativePath}"
fi`;
}).join('\n\n');
const scriptContent = `#!/bin/bash
# 删除未使用的图片文件并从git中移除如果被跟踪
# 生成时间: ${new Date().toLocaleString()}
set -e # 遇到错误时退出
echo "🗑️ 开始删除未使用的图片文件..."
echo "总共需要处理 ${unusedImages.length} 个文件"
echo ""
${deleteScript}
echo ""
echo "✅ 删除操作完成!"
echo "如果有文件从git中移除请记得提交这些更改"
echo "git commit -m 'Remove unused images'"
`;
fs.writeFileSync('./delete-unused-images.sh', scriptContent);
console.log('\n📜 已生成删除脚本: delete-unused-images.sh');
console.log(' 脚本将自动检查文件是否被git跟踪并相应地删除或从git中移除');
console.log(' 运行 chmod +x delete-unused-images.sh && ./delete-unused-images.sh 来删除这些文件');
} else {
console.log('\n🎉 太棒了!所有图片都在使用中!');
}
}
// 检查是否安装了 glob 包
try {
require('glob');
} catch (error) {
console.error('❌ 缺少依赖包 "glob"');
console.log('请运行: npm install glob');
process.exit(1);
}
// 运行分析
analyzeUnusedImages();