190 lines
6.2 KiB
JavaScript
190 lines
6.2 KiB
JavaScript
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); |