如何借助Node.js或Vite检测项目中的未引入无效文件?
检测项目中未被引用的无效文件:Node.js/Vite 实践方案
一、Node.js 原生脚本实现
通过遍历项目文件、解析源码中的引用关系,反向定位未被任何文件引用的无效文件,适合所有Node.js环境的项目。
实现步骤
- 遍历项目目录,收集所有需要检查的源码文件(排除node_modules、dist等无关目录)。
- 解析每个文件的
import/require语句,收集所有被引用的文件路径。 - 以项目入口文件为起点,递归遍历所有依赖,标记所有被使用的文件。
- 对比所有文件与被使用文件列表,输出未被引用的文件。
示例代码
首先安装依赖:
npm install acorn acorn-walk fs-extra
创建find-unused-files.js脚本:
const fs = require('fs-extra'); const acorn = require('acorn'); const walk = require('acorn-walk'); const path = require('path'); // 配置项:根据你的项目调整 const PROJECT_ROOT = path.resolve(__dirname); const EXCLUDES = ['node_modules', '.git', 'dist', '.vite', 'package.json', 'package-lock.json']; const ENTRY_FILES = ['src/main.js']; // 项目入口文件,可添加多个 const VALID_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte']; // 收集项目中所有目标文件 async function getAllProjectFiles(dir) { const files = []; const items = await fs.readdir(dir); for (const item of items) { if (EXCLUDES.includes(item)) continue; const fullPath = path.join(dir, item); const stat = await fs.stat(fullPath); if (stat.isDirectory()) { files.push(...await getAllProjectFiles(fullPath)); } else if (VALID_EXTENSIONS.includes(path.extname(fullPath))) { files.push(path.relative(PROJECT_ROOT, fullPath)); } } return files; } // 解析单个文件的所有相对引用 function getFileImports(filePath) { const imports = []; const fullPath = path.join(PROJECT_ROOT, filePath); try { const content = fs.readFileSync(fullPath, 'utf-8'); // 根据文件类型配置解析器 const parserOptions = { ecmaVersion: 'latest', sourceType: 'module', jsx: filePath.match(/\.(jsx|tsx|vue)$/) ? true : false, typescript: filePath.match(/\.(ts|tsx)$/) ? true : false }; const ast = acorn.parse(content, parserOptions); // 处理ES模块import walk.simple(ast, { ImportDeclaration(node) { resolveAndAddImport(node.source.value, filePath, imports); }, // 处理CommonJS require CallExpression(node) { if (node.callee.name === 'require' && node.arguments[0]?.type === 'Literal') { resolveAndAddImport(node.arguments[0].value, filePath, imports); } } }); } catch (err) { console.warn(`解析文件失败,跳过:${filePath}`, err.message); } return imports; } // 解析相对路径并添加到引用列表 function resolveAndAddImport(rawPath, currentFilePath, imports) { if (!rawPath.startsWith('./') && !rawPath.startsWith('../')) return; const currentDir = path.dirname(path.join(PROJECT_ROOT, currentFilePath)); let resolvedPath = path.resolve(currentDir, rawPath); // 补全省略的扩展名或目录index文件 if (!path.extname(resolvedPath)) { const possiblePaths = [ ...VALID_EXTENSIONS.map(ext => resolvedPath + ext), ...VALID_EXTENSIONS.map(ext => path.join(resolvedPath, `index${ext}`)) ]; resolvedPath = possiblePaths.find(p => fs.existsSync(p)) || resolvedPath; } if (fs.existsSync(resolvedPath)) { imports.push(path.relative(PROJECT_ROOT, resolvedPath)); } } // 主逻辑:找出未被引用的文件 async function findUnusedFiles() { const allFiles = await getAllProjectFiles(PROJECT_ROOT); const usedFiles = new Set(ENTRY_FILES); const queue = [...ENTRY_FILES]; // 递归遍历所有依赖 while (queue.length) { const currentFile = queue.shift(); const imports = getFileImports(currentFile); imports.forEach(imp => { if (!usedFiles.has(imp)) { usedFiles.add(imp); queue.push(imp); } }); } // 筛选未被引用的文件 const unusedFiles = allFiles.filter(file => !usedFiles.has(file)); console.log('=== 未被引用的无效文件 ==='); unusedFiles.forEach(file => console.log(`- ${file}`)); console.log(`总计:${unusedFiles.length}个`); } findUnusedFiles();
使用说明
- 调整
PROJECT_ROOT、ENTRY_FILES等配置项匹配你的项目结构。 - 运行脚本:
node find-unused-files.js。 - 注意:动态生成的引用(如
import(\./components/${name}`)`)无法被静态解析,这类文件需要手动确认。
二、Vite 生态方案
Vite基于Rollup构建,可以直接利用Rollup插件或自定义Vite插件,借助构建过程的依赖图谱快速定位无效文件。
方案1:使用现成Rollup插件
rollup-plugin-unused-files可以直接集成到Vite配置中,在构建时自动输出未被引用的文件。
- 安装插件:
npm install rollup-plugin-unused-files --save-dev
- 修改
vite.config.js:
import { defineConfig } from 'vite'; import unusedFiles from 'rollup-plugin-unused-files'; export default defineConfig({ plugins: [ unusedFiles({ root: process.cwd(), include: ['src/**/*.{js,jsx,ts,tsx,vue}'], // 要检查的文件 exclude: ['node_modules', 'dist'], // 排除目录 entry: ['src/main.js'] // 项目入口 }) ] });
- 运行构建命令:
vite build
插件会在构建完成后,在控制台输出未被引用的文件列表。
方案2:自定义Vite插件
如果需要更灵活的逻辑,可以编写自定义插件,在Vite构建过程中收集所有被处理的模块,再对比项目文件:
import { defineConfig } from 'vite'; import fs from 'fs-extra'; import path from 'path'; function viteUnusedFilesPlugin() { const projectRoot = process.cwd(); const excludes = ['node_modules', '.git', 'dist', '.vite']; const validExts = ['.js', '.jsx', '.ts', '.tsx', '.vue']; const usedFiles = new Set(); return { name: 'vite-plugin-unused-files', // 收集所有被Vite处理的模块 moduleParsed(moduleInfo) { const modulePath = moduleInfo.id; if (modulePath.startsWith(projectRoot) && !excludes.some(ex => modulePath.includes(ex))) { const relativePath = path.relative(projectRoot, modulePath); usedFiles.add(relativePath); } }, // 构建完成后对比所有项目文件 async buildEnd() { const allFiles = []; const traverseDir = async (dir) => { const items = await fs.readdir(dir); for (const item of items) { if (excludes.includes(item)) continue; const fullPath = path.join(dir, item); const stat = await fs.stat(fullPath); if (stat.isDirectory()) { await traverseDir(fullPath); } else if (validExts.includes(path.extname(fullPath))) { allFiles.push(path.relative(projectRoot, fullPath)); } } }; await traverseDir(projectRoot); const unusedFiles = allFiles.filter(file => !usedFiles.has(file)); console.log('\n=== 未被引用的无效文件 ==='); unusedFiles.forEach(file => console.log(`- ${file}`)); console.log(`总计:${unusedFiles.length}个`); } }; } export default defineConfig({ plugins: [viteUnusedFilesPlugin()] });
运行vite build即可看到结果。
使用说明
- Vite插件仅能检测被构建流程处理的文件,如果有未被Vite处理的静态资源或配置文件,需要手动调整
include规则。 - 动态导入的模糊路径同样无法被检测,需人工复核。
内容的提问来源于stack exchange,提问作者tocka masked
相关产品推荐
相关产品推荐

