如何在TypeScript转换器中检测导入路径是否为目录?
检测TypeScript导入/导出路径是否为目录的实现方法
要实现给目录导入追加/index.js、给文件导入追加.js的需求,核心是通过TypeScript的模块解析能力结合文件系统校验,判断导入路径对应的是目录还是文件。
实现步骤
- 利用TypeScript Program的模块解析能力:通过
resolveModuleName方法准确获取TS实际解析的模块路径,适配tsconfig中的baseUrl、paths等配置。 - 文件系统校验:通过
fs模块判断导入路径对应的物理资源是否为目录。 - 修改Transformer逻辑:根据检测结果生成对应的模块标识符。
完整修改后的代码
import typescript from "typescript"; import * as fs from "fs"; import * as path from "path"; export default function transformer(program: typescript.Program) { return (transformationContext: typescript.TransformationContext) => (sourceFile: typescript.SourceFile) => { function shouldMutateModuleSpecifier(node: typescript.Node): node is typescript.ImportDeclaration | typescript.ExportDeclaration { if (!typescript.isImportDeclaration(node) && !typescript.isExportDeclaration(node)) { return false; } if (!node.moduleSpecifier || !typescript.isStringLiteral(node.moduleSpecifier)) { return false; } const specifier = node.moduleSpecifier.text; // 仅处理相对/绝对路径的无扩展名导入,排除第三方模块 return (specifier.startsWith('./') || specifier.startsWith('../') || specifier.startsWith('/')) && !path.extname(specifier); } function isDirectoryImport(moduleSpecifier: string, sourceFile: typescript.SourceFile): boolean { const baseDir = path.dirname(sourceFile.fileName); const resolvedPath = path.resolve(baseDir, moduleSpecifier); // 直接校验物理路径是否为目录 if (fs.existsSync(resolvedPath)) { return fs.statSync(resolvedPath).isDirectory(); } // 利用TS内置模块解析,判断是否指向目录下的index文件 const resolveResult = typescript.resolveModuleName( moduleSpecifier, sourceFile.fileName, program.getCompilerOptions(), program.getModuleResolutionHost() ); if (resolveResult.resolvedModule) { const resolvedFileName = resolveResult.resolvedModule.resolvedFileName; const resolvedDir = path.dirname(resolvedFileName); // 若解析结果是index文件,且原路径对应该目录,则判定为目录导入 return path.basename(resolvedFileName).startsWith('index') && resolvedDir === path.resolve(baseDir, moduleSpecifier); } return false; } function visitNode(node: typescript.Node): typescript.VisitResult<typescript.Node> { if (shouldMutateModuleSpecifier(node)) { let newSpecifierText = node.moduleSpecifier.text; if (isDirectoryImport(newSpecifierText, sourceFile)) { newSpecifierText = `${newSpecifierText}/index.js`; } else { newSpecifierText = `${newSpecifierText}.js`; } const newModuleSpecifier = typescript.factory.createStringLiteral(newSpecifierText); if (typescript.isImportDeclaration(node)) { return typescript.factory.updateImportDeclaration( node, node.modifiers, node.importClause, newModuleSpecifier, undefined ); } if (typescript.isExportDeclaration(node)) { return typescript.factory.updateExportDeclaration( node, node.modifiers, false, node.exportClause, newModuleSpecifier, undefined ); } } return typescript.visitEachChild(node, visitNode, transformationContext); } return typescript.visitNode(sourceFile, visitNode); }; }
关键逻辑说明
shouldMutateModuleSpecifier:过滤出需要处理的导入/导出声明,仅针对无扩展名的相对/绝对路径导入,避免修改第三方模块或已有扩展名的文件。isDirectoryImport:- 先通过
fs.existsSync和fs.statSync直接校验物理路径是否为目录; - 若物理路径不存在(比如TS通过
paths映射的目录),则用resolveModuleName获取TS的解析结果,判断是否指向目录下的index文件。
- 先通过
- 模块标识符生成:根据检测结果,目录导入追加
/index.js,文件导入追加.js。
内容的提问来源于stack exchange,提问作者Carlo Corradini
相关产品推荐
相关产品推荐

