如何用TypeScript Checker从CommonJS模块重导出提取别名文件路径?
问题背景
在tsc v5.2.2中,针对ES模块可以通过checker和program轻松提取导出的文件引用:
ES模块示例代码
import {child} from './childFile'; const local = 'local'; export {child, local}
ES模块提取逻辑
const fileSymbol = checker.getSymbolAtLocation(sourceFile); fileSymbol?.exports?.forEach((symbol) => { console.log(symbol.name); //'local' or 'child' const nodes = symbol.declarations!; console.log(ts.SyntaxKind[nodes[0].kind]); //ExportSpecifier const targets = checker.getAliasedSymbol(symbol)?.declarations!; console.log(targets[0].getSourceFile().fileName); //'./localFile' or './childFile' });
但针对等价的CommonJS模块,无法复用上述逻辑:
CommonJS模块示例代码
const {child} = require('./child'); const local = 'local'; exports = {child, local}
尝试过的方案及问题
- 使用
module: NodeNext编译选项时,checker.getSymbolAtLocation(sourceFile)返回undefined - 访问
source.locals能获取到child和local的符号,但处理BindingElement时,checker.getAliasedSymbol返回undefined,无法追踪到对应的文件引用
解决方案
CommonJS模块的导出是通过赋值语句实现的,TypeScript不会为其生成ES模块那样的fileSymbol.exports,需要手动解析赋值节点并追踪变量来源:
具体实现代码
// 1. 定位到exports赋值语句 const exportsAssignment = sourceFile.statements.find( stmt => ts.isExpressionStatement(stmt) && ts.isAssignmentExpression(stmt.expression) && stmt.expression.left.getText() === "exports" ) as ts.ExpressionStatement | undefined; if (!exportsAssignment) return; // 2. 获取导出对象字面量 const exportObj = (exportsAssignment.expression as ts.AssignmentExpression).right; if (!ts.isObjectLiteralExpression(exportObj)) return; // 3. 遍历每个导出属性,追踪来源 exportObj.properties.forEach(prop => { if (!ts.isPropertyAssignment(prop)) return; const exportName = prop.name.getText(); // 获取属性对应的变量引用 const valueExpr = prop.initializer; if (!ts.isIdentifier(valueExpr)) return; const varSymbol = checker.getSymbolAtLocation(valueExpr); if (!varSymbol?.declarations) return; // 处理来自require解构的变量(如child) const bindingDecl = varSymbol.declarations.find(ts.isBindingElement) as ts.BindingElement | undefined; if (bindingDecl) { // 找到父级的变量声明 const varDecl = bindingDecl.parent.parent as ts.VariableDeclaration; // 定位require调用 const requireCall = varDecl.initializer; if (ts.isCallExpression(requireCall) && requireCall.expression.getText() === "require") { const pathArg = requireCall.arguments[0]; if (ts.isStringLiteral(pathArg)) { // 解析文件路径为真实文件名 const resolveResult = program.resolveModuleName( pathArg.text, sourceFile.fileName, program.getCompilerOptions(), program.getModuleResolutionHost() ); const targetFile = resolveResult.resolvedModule?.resolvedFileName; console.log(`导出 ${exportName} 来自文件: ${targetFile}`); } } } // 处理本地变量(如local) else if (varSymbol.declarations.some(ts.isVariableDeclaration)) { console.log(`导出 ${exportName} 是本地变量`); } });
关键说明
- CommonJS的
exports赋值是普通表达式,需要手动遍历语句找到目标节点 - 对于解构自
require的变量,需要逐层追踪到require调用表达式,提取路径后用program.resolveModuleName解析(和TypeScript模块解析逻辑一致) - 本地变量可通过判断声明类型直接识别
内容的提问来源于stack exchange,提问作者Michael Jonker
相关产品推荐
相关产品推荐

