如何通过代码获取Node.js模块中的类/模块列表?
获取Node.js模块中的类/模块列表
要实现类似IntelliSense的模块成员提取,主要有静态分析和动态加载两种方案,考虑到你是做代码生成器(大概率要避免执行模块的副作用),优先推荐静态分析方案,以下是具体实现:
方案1:静态分析模块代码(推荐,无执行风险)
通过解析模块的AST(抽象语法树)提取导出的类、函数等成员,无需执行模块代码,适合处理未知或不可信的模块。
步骤:
- 安装依赖:
@babel/parser(解析JS/TS代码)和@babel/traverse(遍历AST)
npm install @babel/parser @babel/traverse
- 编写解析代码:
const fs = require('fs'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; function extractExports(modulePath) { const code = fs.readFileSync(modulePath, 'utf8'); // 解析代码生成AST,支持JSX、TS等语法,可根据模块类型调整配置 const ast = parser.parse(code, { sourceType: 'module', plugins: ['typescript', 'jsx'] }); const exports = { namedExports: [], defaultExport: null }; traverse(ast, { // 处理命名导出(export class X {} / export const Y = ...) ExportNamedDeclaration(path) { // 处理直接声明的导出 path.node.declaration?.declarations?.forEach(decl => { if (decl.id?.name) { const isClass = path.node.declaration.type === 'ClassDeclaration'; exports.namedExports.push({ name: decl.id.name, type: isClass ? 'class' : 'variable' }); } }); // 处理重导出(export { X } from './other') path.node.specifiers?.forEach(spec => { exports.namedExports.push({ name: spec.exported.name, type: 're-export' }); }); }, // 处理默认导出(export default class X {} / export default ...) ExportDefaultDeclaration(path) { const node = path.node.declaration; if (node.type === 'ClassDeclaration' && node.id?.name) { exports.defaultExport = { name: node.id.name, type: 'class' }; } else if (node.type === 'Identifier') { exports.defaultExport = { name: node.name, type: 'reference' }; } } }); return exports; } // 使用示例 const moduleExports = extractExports('./your-module.js'); console.log('命名导出:', moduleExports.namedExports); console.log('默认导出:', moduleExports.defaultExport);
方案2:动态加载模块(仅适合信任的模块)
直接加载模块并遍历导出对象,这种方式会执行模块代码,可能触发副作用(比如数据库连接、文件写入),只适合处理你完全信任的模块。
async function loadModuleExports(modulePath) { // 用ES模块的import(需Node.js支持,或配置package.json type:module) const moduleExports = await import(modulePath); const exportsInfo = []; for (const [key, value] of Object.entries(moduleExports)) { // 简单判断是否为类:类的typeof是'function',且prototype不可枚举 const isClass = typeof value === 'function' && !Object.prototype.hasOwnProperty.call(value, 'prototype'); exportsInfo.push({ name: key, type: isClass ? 'class' : typeof value }); } return exportsInfo; } // 使用示例 loadModuleExports('./your-module.js').then(console.log);
方案3:解析TypeScript类型定义(如果模块有.d.ts)
如果目标模块是TypeScript编写的,或带有.d.ts类型声明文件,可以直接解析类型定义获取更准确的成员信息:
const ts = require('typescript'); function extractTsExports(dtsPath) { const program = ts.createProgram([dtsPath], {}); const sourceFile = program.getSourceFile(dtsPath); const exports = []; ts.forEachChild(sourceFile, node => { if (ts.isExportDeclaration(node)) { node.specifiers?.forEach(spec => { if (ts.isExportSpecifier(spec)) { exports.push({ name: spec.name.text, type: 'named-export' }); } }); if (node.declaration && ts.isClassDeclaration(node.declaration)) { exports.push({ name: node.declaration.name?.text, type: 'class' }); } } }); return exports; } // 使用示例 const tsExports = extractTsExports('./your-module.d.ts'); console.log(tsExports);
注意事项
- 静态分析需要覆盖各种语法场景(比如解构导出、匿名类默认导出等),可根据你的实际模块类型扩展AST遍历逻辑。
- 动态加载需警惕模块的副作用,比如模块初始化时的异步操作或全局变量修改。
- 第三方模块优先用静态分析或解析其类型声明文件,避免不必要的执行。
内容的提问来源于stack exchange,提问作者Josh Russo
相关产品推荐
相关产品推荐

