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

如何获取TypeScript编译器在项目中用到的所有node_modules文件?

获取TypeScript项目依赖的node_modules文件

你可以通过TypeScript Compiler API的Program对象分析项目的依赖关系,精准提取出项目源文件直接或间接依赖的node_modules中的文件,具体实现如下:

核心思路

  1. 利用ts.createProgram生成项目编译程序,获取所有源文件的依赖解析能力
  2. 遍历项目自身的源文件,解析每个文件的导入/require语句,找到对应的模块路径
  3. 递归追踪依赖链,筛选出路径包含node_modules的文件并去重收集

修改后的完整代码

import * as fs from "fs"
import * as ts from "typescript"

// 递归收集所有依赖的node_modules文件
function collectNodeModulesDeps(
    program: ts.Program,
    filePath: string,
    collected: Set<string>,
    processedFiles: Set<string>
): void {
    if (processedFiles.has(filePath)) return;
    processedFiles.add(filePath);

    const sourceFile = program.getSourceFile(filePath);
    if (!sourceFile) return;

    // 处理ES模块导入/导出
    ts.forEachChild(sourceFile, node => {
        let moduleSpecifier: string | undefined;

        if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
            const specifier = node.moduleSpecifier;
            if (specifier && ts.isStringLiteral(specifier)) {
                moduleSpecifier = specifier.text;
            }
        } 
        // 处理CommonJS require调用
        else if (ts.isCallExpression(node) && node.expression.getText() === 'require') {
            const arg = node.arguments[0];
            if (arg && ts.isStringLiteral(arg)) {
                moduleSpecifier = arg.text;
            }
        }

        if (moduleSpecifier) {
            const resolvedModule = program.getResolvedModule(filePath, moduleSpecifier);
            if (resolvedModule && resolvedModule.resolvedFileName) {
                const resolvedPath = resolvedModule.resolvedFileName;
                // 筛选node_modules中的文件
                if (resolvedPath.includes('node_modules')) {
                    collected.add(resolvedPath);
                }
                // 递归追踪该模块的依赖,确保收集到间接依赖
                collectNodeModulesDeps(program, resolvedPath, collected, processedFiles);
            }
        }
    });
}

function compile(fileNames: string[], options: ts.CompilerOptions): void {
    const createdFiles: { [key: string]: string } = {}
    const host = ts.createCompilerHost(options);
    host.writeFile = (fileName: string, contents: string) => {
        console.log(">>", fileName)
        createdFiles[fileName] = contents
    }

    const program = ts.createProgram(fileNames, options, host);
    
    // 初始化收集容器
    const nodeModulesDeps = new Set<string>();
    const processedFiles = new Set<string>();
    const projectFiles = new Set(fileNames);

    // 遍历所有项目源文件,启动依赖收集
    projectFiles.forEach(file => {
        collectNodeModulesDeps(program, file, nodeModulesDeps, processedFiles);
    });

    // 输出收集到的node_modules依赖文件
    console.log('\n=== 依赖的node_modules文件 ===');
    nodeModulesDeps.forEach(file => console.log(file));

    // 执行编译输出
    program.emit();
}

const parseConfigHost = {
    fileExists: fs.existsSync,
    readDirectory: ts.sys.readDirectory,
    readFile: (file: string) => fs.readFileSync(file, 'utf8'),
    useCaseSensitiveFileNames: true,
}

const configResult = ts.parseJsonConfigFileContent(
    ts.readConfigFile('./tsconfig.json', ts.sys.readFile).config,
    parseConfigHost,
    "."
)

compile(configResult.fileNames, configResult.options)

关键说明

  • 模块解析兼容性:同时支持ES模块(import/export)和CommonJS(require)的依赖解析,确保覆盖项目中所有模块引用方式
  • 递归收集:通过递归追踪每个依赖模块的自身依赖,能完整收集到直接和间接用到的node_modules文件
  • 精准筛选:仅收集项目实际引用的模块文件,不会包含node_modules中未被使用的冗余文件
  • 可定制化:如果不需要类型定义文件(.d.ts),可以在筛选条件中添加&& !resolvedPath.endsWith('.d.ts')

内容的提问来源于stack exchange,提问作者Corno

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 13:15:40