You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何借助Node.js或Vite检测项目中的未引入无效文件?

检测项目中未被引用的无效文件:Node.js/Vite 实践方案

一、Node.js 原生脚本实现

通过遍历项目文件、解析源码中的引用关系,反向定位未被任何文件引用的无效文件,适合所有Node.js环境的项目。

实现步骤

  1. 遍历项目目录,收集所有需要检查的源码文件(排除node_modules、dist等无关目录)。
  2. 解析每个文件的import/require语句,收集所有被引用的文件路径。
  3. 以项目入口文件为起点,递归遍历所有依赖,标记所有被使用的文件。
  4. 对比所有文件与被使用文件列表,输出未被引用的文件。

示例代码

首先安装依赖:

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配置中,在构建时自动输出未被引用的文件。

  1. 安装插件:
npm install rollup-plugin-unused-files --save-dev
  1. 修改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'] // 项目入口
    })
  ]
});
  1. 运行构建命令:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 00:52:41