迁移至 TypeScript 7 前需梳理的兼容性问题与 CI 优化
٢٩ يوليو ٢٠٢٦
0
Computing/SoftwareRelated Video
8:32TypeScript 7 正式发布:速度快得让人吃惊
Better Stack
Comments (0)
Log in to leave a comment
No posts yet
8:32Better Stack
Log in to leave a comment
No posts yet
TypeScript 7 采用了 Go 语言重写编译器。在类型检查速度提升最高达 12 倍的利好面前,维护大型 Monorepo 的团队想必早已跃跃欲试。实际上,Vanta 和 VS Code 工程团队均表示其 CI 流水线的构建时间减少了 80% 以上。
然而,如果盲目应用,整个 CI 可能会直接崩溃。这是由于切换至原生二进制文件后,官方将对原有静态分析 API 的支持推迟到了后续版本。像 @typescript-eslint 或 ts-morph 这类依赖 require('typescript') 来查看内部 API 的工具将集体失效。虽然性能非常具有吸引力,但工具链的破坏则是另一回事。为了在防止部署中断的同时享受到速度优势,需要采用一些变通方案。
TypeScript 7.0 的 tsc 二进制文件完全禁止了 Node.js 内部模块调用。像以往那样在 JS 环境中导入使用的软件包,将在构建阶段立即报错。如果在毫无准备的情况下直接升级版本,会导致整个 CI 流水线瘫痪。
更安全的做法是在流水线的最顶层挂载诊断脚本。通过扫描工作区内的所有 package.json 和 tsconfig.json,找出 TS 7 所拒绝的选项或软件包。如果残留了 ignoreDeprecations 或 target: es5 等旧版本配置,则使其立即抛出错误并终止进程。
`javascript
// scripts/check-ts7-compatibility.mjs
import fs from 'node:fs';
import { globSync } from 'glob';
const INCOMPATIBLE_DEPS = [
'ts-morph',
'ts-node',
'@babel/plugin-transform-typescript',
'typescript-eslint',
'@typescript-eslint/parser'
];
const DEPRECATED_TSCONFIG_OPTIONS = ['target:es5', 'moduleResolution:node', 'baseUrl', 'ignoreDeprecations'];
function runDiagnostics() {
console.log('开始进行 TypeScript 7 兼容性事前诊断...');
let hasError = false;
const packageFiles = globSync('/package.json', { ignore: '/node_modules/' });
for (const file of packageFiles) {
const content = JSON.parse(fs.readFileSync(file, 'utf8'));
const allDeps = { ...content.dependencies, ...content.devDependencies };
for (const dep of INCOMPATIBLE_DEPS) {
if (allDeps[dep]) {
console.warn([依赖项警告] ${file}: '${dep}' 软件包与 TS7 原生 API 不兼容。);
hasError = true;
}
}
}
const tsconfigFiles = globSync('/tsconfig*.json', { ignore: '/node_modules/' });
for (const file of tsconfigFiles) {
const rawContent = fs.readFileSync(file, 'utf8');
for (const opt of DEPRECATED_TSCONFIG_OPTIONS) {
if (rawContent.includes(opt)) {
console.error([配置错误] ${file}: 发现失效选项 -> '${opt}');
hasError = true;
}
}
}
if (hasError) process.exit(1);
}
runDiagnostics();
`
依赖 ts.createProgram() 或 ts.transform() 的自定义 AST 转换器(Transformers)同样无法工作。TS 7.0 不接受外部注入 JS 插件。这类逻辑必须替换为 SWC 或 Babel 等 Rust/C++ 绑定模块,或者剥离至编译阶段之外。
| 编译器选项 | TypeScript 6.0 行为 | TypeScript 7.0 行为 | 应对方法 |
|---|---|---|---|
target |
使用 es5 时发出警告 |
Hard Error (中断编译) | 更改为 es2022 或更高版本 |
moduleResolution |
允许 node 设置 |
Hard Error | 更改为 bundler 或 node16 |
baseUrl |
允许单独使用 | Hard Error | 切换为单独使用 compilerOptions.paths |
ignoreDeprecations |
忽略警告生效 | 选项失效并报错 | 完全删除该设置 |
strict |
默认值为 false |
默认值为 true |
在 tsconfig.json 中明确设定值 |
TypeScript 7 利用了 Go 的线程模型。它提供了用于并行处理类型检查的新选项 --checkers,以及用于控制项目引用构建的 --builders 选项。Slack 工程团队结合使用这些选项,将类型检查时间从 20 分钟缩减至 4.5 分钟。但是,如果不契合 CPU 核心数而过度分配线程,会导致 CPU 上下文切换开销增大,甚至因 OOM (Out of Memory) 导致构建崩溃。
建议根据 Runner 的规格,按照以下公式来设定线程数。假设虚拟机的分配核心数为 ,总内存为 ,OS 预留内存为 (),每个 Worker 的平均内存占用量为 (),则单个项目的最佳线程数 计算如下:
N_{checkers} = minleft( C_{vCPU}, leftlfloor rac{M_{total} - M_{OS}}{M_{worker}} ight floor ight)总线程数之和 不应超过 。例如,在 8 vCPU / 16GB 的 Runner 环境下,设置 --checkers 4、--builders 2 较为合适。
`yaml}
name: Monorepo Parallel Typecheck
on:
push:
branches: [main]
jobs:
typecheck:
runs-on: ubuntu-latest-8-core
steps:
- name: Checkout Codebase
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Purge Legacy TS 6.0 Cache
run: find . -name "*.tsbuildinfo" -not -path "*/node_modules/*" -delete
- name: Execute TS 7 Parallel Check
run: npx tsc --build --checkers 4 --builders 2 --verbose
`
需要注意的是:TS 7 的增量编译引擎与原先 TS 6 的 .tsbuildinfo 缓存文件格式并不兼容。如果在构建前没有通过 find . -name "*.tsbuildinfo" -delete 清理旧版本缓存,会导致段错误 (Segmentation Fault)。
面向编辑器的语言服务器也重构为了基于 Go 二进制文件的 LSP。根据 AWS CodeBuild 的测试结果,在大型 Monorepo 中打开文件到首次显示类型错误所需的时间从 17.5 秒缩短至 1.3 秒。
要统一团队成员的 VS Code 环境,只需在 Monorepo 根目录的 .vscode/settings.json 中添加以下配置即可:
`json
{
"typescript.tsdk": "node_modules/typescript/lib",
"js/ts.experimental.useTsgo": true,
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.preferences.preferTypeOnlyAutoImports": true,
"files.associations": {
"*.tsbuildinfo": "json"
}
}
`
偶尔会出现与 Vue 或 Svelte 等模板语言插件发生冲突,导致语法解析崩溃的情况。遇到这种情况时,可以在命令面板 (Ctrl+Shift+P) 中点击 TypeScript: Select TypeScript Version...,切回原有的 TS 6.0 引擎继续工作。
如果项目中缠绕着遗留软件包,暂时无法将 ESLint 升级至 TS 7 环境,可以通过配置双引擎(Dual Engine)并行的方式来绕过该问题。即向静态分析工具连接 TS 6 API,而将实际的类型检查与构建任务交给 TS 7 原生二进制文件。
在 package.json 中指定包别名 (Alias) 以同时安装这两个编译器:
`json
{
"name": "monorepo-root",
"private": true,
"devDependencies": {
"typescript": "npm:@typescript/typescript6@^6.0.2",
"@typescript/native": "npm:typescript@^7.0.2",
"eslint": "^9.0.0",
"typescript-eslint": "^8.0.0"
},
"scripts": {
"typecheck": "ts-native --build",
"typecheck:legacy": "tsc6 --noEmit",
"lint": "eslint ."
}
}
`
在这种状态下,为 CI 附上影子构建 (Shadow Build) 脚本会更加安全。通过 diff 对比原 TS 6 与 TS 7 的诊断结果,能够监控在模板字面量类型或条件类型推导中结果是否发生变化。
`bash
#!/usr/bin/env bash
set -e
echo "=== 1. 生成原有 TS 6.0 编译器诊断输出 ==="
npx tsc6 --noEmit --pretty false > ./ts6-baseline.log 2>&1 || true
echo "=== 2. 生成新版 TS 7.0 原生编译器诊断输出 ==="
npx --package @typescript/native tsc --noEmit --pretty false > ./ts7-output.log 2>&1 || true
echo "=== 3. 诊断结果 Diff 比对验证 ==="
DIFF_RESULT=$(diff ./ts6-baseline.log ./ts7-output.log || true)
if [ -z "DIFF_RESULT"
exit 0
fi
`
在工具链兼容性问题彻底解决之前,将 Lint 和 Typecheck 的职责拆开运行是更为现实的选择。通过事前诊断脚本清理可能引发问题的选项,并根据 CI 基础设施规格微调线程数,便能在不导致部署流水线停摆的情况下享受速度提升带来的收益。