const ts = require('typescript'); const path = require('path'); const fs = require('fs'); const { cwd } = require('process'); // 解析命令行参数 function parseArgs() { const args = process.argv.slice(2); let configPath = 'tsconfig.json'; for (let i = 0; i < args.length; i++) { if (args[i] === '-p' || args[i] === '--project') { if (i + 1 < args.length) { configPath = args[i + 1]; break; } else { console.error('error: option \'-p, --project\' argument missing'); process.exit(1); } } } return configPath; } // ANSI 颜色代码 const colors = { reset: '\x1b[0m', cyan: '\x1b[36m', red: '\x1b[91m', yellow: '\x1b[93m', gray: '\x1b[90m' }; function compile(configPath) { // 读取 tsconfig.json const configFile = ts.readConfigFile(configPath, ts.sys.readFile); if (configFile.error) { console.error(ts.formatDiagnostic(configFile.error, { getCanonicalFileName: f => f, getCurrentDirectory: process.cwd, getNewLine: () => '\n' })); process.exit(1); } // 解析配置 const parsedConfig = ts.parseJsonConfigFileContent( configFile.config, ts.sys, path.dirname(configPath) ); if (parsedConfig.errors.length > 0) { parsedConfig.errors.forEach(diagnostic => { console.error(ts.formatDiagnostic(diagnostic, { getCanonicalFileName: f => f, getCurrentDirectory: process.cwd, getNewLine: () => '\n' })); }); process.exit(1); } // 创建编译程序 // 根据配置决定是否使用增量编译 let program; if (parsedConfig.options.incremental || parsedConfig.options.composite) { // 对于增量编译,使用 createIncrementalProgram const host = ts.createIncrementalCompilerHost(parsedConfig.options); program = ts.createIncrementalProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options, host: host, configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(parsedConfig), }); } else { // 普通编译 program = ts.createProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options, }); } // 执行编译 const emitResult = program.emit(); // 获取诊断信息 const allDiagnostics = ts .getPreEmitDiagnostics(program) .concat(emitResult.diagnostics); // 输出诊断信息 allDiagnostics.forEach(diagnostic => { if (diagnostic.file) { const { line, character } = diagnostic.file.getLineAndCharacterOfPosition( diagnostic.start ); const message = ts.flattenDiagnosticMessageText( diagnostic.messageText, '\n' ); const isError = diagnostic.category === ts.DiagnosticCategory.Error; const category = isError ? 'error' : 'warning'; const categoryColor = isError ? colors.red : colors.yellow; console.log( `${colors.cyan}${diagnostic.file.fileName}${colors.reset}:${colors.yellow}${line + 1}${colors.reset}:${colors.yellow}${character + 1}${colors.reset} - ${categoryColor}${category}${colors.reset} ${colors.gray}TS${diagnostic.code}${colors.reset}: ${message}` ); } else { const isError = diagnostic.category === ts.DiagnosticCategory.Error; const category = isError ? 'error' : 'warning'; const categoryColor = isError ? colors.red : colors.yellow; console.log( `${categoryColor}${category}${colors.reset} ${colors.gray}TS${diagnostic.code}${colors.reset}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}` ); } }); // 输出编译统计 const errorCount = allDiagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length; const warningCount = allDiagnostics.filter(d => d.category === ts.DiagnosticCategory.Warning).length; if (errorCount > 0 || warningCount > 0) { if (allDiagnostics.length > 0) { console.log(''); } const parts = []; if (errorCount > 0) { parts.push(`${errorCount} error${errorCount !== 1 ? 's' : ''}`); } if (warningCount > 0) { parts.push(`${warningCount} warning${warningCount !== 1 ? 's' : ''}`); } console.log(`Found ${parts.join(' and ')}.`); } // tsc 的行为: // 1. 默认情况下(noEmitOnError: false): // - 即使有类型错误也会生成 .js 文件 // - 但如果有错误,退出码是 1 // 2. noEmitOnError: true 时: // - 有错误时不生成文件(emitSkipped 为 true) // - 退出码是 1 // 3. 没有错误时: // - 生成文件,退出码 0 // 无论 emitSkipped 与否,只要有错误就应该退出 1 if (errorCount > 0) { process.exit(1); } console.log('Compilation completed successfully.'); } // 执行编译 const configPathArg = parseArgs(); let configPath; // 判断参数是目录还是文件 if (fs.existsSync(configPathArg)) { const stat = fs.statSync(configPathArg); if (stat.isDirectory()) { // 如果是目录,拼接 tsconfig.json configPath = path.resolve(configPathArg, 'tsconfig.json'); } else { // 如果是文件,直接使用 configPath = path.resolve(configPathArg); } } else { // 尝试作为相对路径解析 configPath = path.resolve(cwd(), configPathArg); if (!fs.existsSync(configPath)) { // 如果还是不存在,尝试添加 tsconfig.json const dirPath = path.resolve(cwd(), configPathArg); if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) { configPath = path.join(dirPath, 'tsconfig.json'); } } } if (!fs.existsSync(configPath)) { console.error(`error TS5058: The specified path does not exist: '${configPath}'.`); process.exit(1); } compile(configPath);