tsc -b 跑了两分多钟还没有结束,编辑器里的红色波浪线要等十几秒才出现。项目代码没有突然变大,开发机也没有明显变慢,真正拖住反馈速度的,可能是编译器本身。对大型 TypeScript 项目来说,类型检查等待时间会直接打断开发节奏,也会把 CI 的排队时间放大。
TypeScript 7.0 把这件事推到了一个新节点。2026 年 7 月 8 日,微软发布了 TypeScript 7.0。它不是在旧编译器上做几处局部优化,而是把编译器移植成用 Go 编写的原生实现,再利用共享内存和多线程处理更多工作。官方称完整构建通常可以提速 8 到 12 倍。
这份提速很诱人,升级动作却不能只改一行版本号。TypeScript 7.0 暂不提供旧的编程 API,编辑器语言服务、构建插件、代码检查工具、单仓库依赖关系都要重新盘点。下面按七个检查点走一遍,把能直接执行的命令、判断标准和回滚边界说清楚。
性能收益来自哪里
旧版本编译器运行在 JavaScript 引擎里。解析源文件、建立类型关系、检查错误、生成输出,这些步骤既要承担运行时和垃圾回收的开销,也很难把所有工作拆成足够有效的并发任务。原生端口减少了运行时层面的负担,又为解析、类型检查和生成阶段提供了并行空间。
这不意味着每个项目都会得到同样的倍数。代码库规模、项目引用数量、磁盘速度、内存容量和构建命令都会影响结果。完整冷构建通常更容易观察到收益,短小项目或者主要依赖缓存的增量构建,体感可能没有官方示例那么夸张。
微软官方博客给出的开源代码库对比如下,表里的数字是同一博客公布的基准结果,不是本文在本地重新测出的成绩。
| 代码库 | TypeScript 6.0 | TypeScript 7.0 | 官方给出的提速 |
|---|---|---|---|
| VS Code | 125.7 秒 | 10.6 秒 | 11.9 倍 |
| Sentry | 139.8 秒 | 15.7 秒 | 8.9 倍 |
| Bluesky | 24.3 秒 | 2.8 秒 | 8.7 倍 |
| Playwright | 12.8 秒 | 1.47 秒 | 8.7 倍 |
| tldraw | 11.2 秒 | 1.46 秒 | 7.7 倍 |
同一篇官方资料还给出了编辑器反馈的对比。在 VS Code 代码库中,从打开文件到看到第一个错误,时间由约 17.5 秒降到 1.3 秒以内。内存变化也不是单向增加,官方示例中的 VS Code 构建内存由 5.2GB 降到 4.2GB,Bluesky 由 1.8GB 降到 1.3GB。这里的关键不是记住某个漂亮数字,而是理解收益集中在大型工程的反馈环路。
检查一:盘点编程 API 依赖
迁移前最需要确认的不是项目能不能执行 tsc,而是周边工具有没有把 TypeScript 当作一个编程库来调用。TypeScript 7.0 不提供旧的编程 API。项目里如果有代码直接导入 typescript,或者调用 createProgram、transpileModule、getPreEmitDiagnostics 一类接口,就不能把主依赖直接切到 7.0 后再等待报错。
可以在仓库根目录做一次搜索,先找到显式依赖。命令只用于盘点,不代表搜索到一行就一定不能升级,最终还要看工具的版本说明和实际调用链。
perl
git grep -n "from 'typescript'"
git grep -n "require('typescript')"
git grep -n "ts.createProgram"
git grep -n "ts.transpileModule"
还要检查间接依赖。构建脚本、测试配置、代码检查配置和编辑器插件都可能在内部加载 TypeScript。可以查看依赖树,确认哪些包把它声明成 peer dependency,再在一个干净目录执行安装和构建。不要只看应用源码没有导入,就判断整个工程没有 API 风险。
盘点结果可以分成三类。只执行命令行 tsc 的项目,迁移阻力最低。使用工具但工具已经明确支持原生编译器的项目,可以进入灰度。依赖旧 API 的项目,需要保留 6.0 作为工具链版本,等待兼容版本或者使用官方提供的并行过渡方式。这个分类比盯着一个全局版本号更可靠。
检查二:让 6 和 7 并行工作
TypeScript 官方给出的过渡思路,是用兼容包保留 6.0 的可编程接口,同时让 7.0 负责新的命令行编译。兼容包是 @typescript/typescript6,它提供 tsc6 可执行文件,并重新导出 TypeScript 6.0 的 API。
官方博客展示的 npm alias 结构可以写成下面这样。这里保留了原来的 typescript 包名给旧 API 依赖,把 7.0 的包放到另一个别名下。
perl
{
"devDependencies": {
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2"
}
}
采用这个结构前,要先确认团队使用的包管理器能正确处理 npm alias,并把变更写入锁文件。安装后不要凭文件内容猜测解析结果,分别执行版本检查和目标命令。正常的过渡状态是,原生编译器和兼容编译器都有可追踪的入口,依赖旧 API 的工具仍然解析到 6.0,项目的构建命令则按团队约定指向 7.0。
如果工具只是把 typescript 当作命令行程序调用,可以单独升级它。如果工具需要从 typescript 包导入对象,就要让它继续读取兼容包。两种用途不要混在同一个无说明的依赖名里,否则下次清理 package.json 或重新安装时,很容易把兼容层删掉。
并行运行不是永久复制两套依赖。它的作用是给迁移留下观察窗口。等工具生态完成适配,再删除兼容包和 alias。过渡期间要把这段理由写进项目文档,让后来维护依赖的人知道这不是重复安装造成的浪费。
检查三:固定版本并建立基线
GitHub 官方 release 信息显示,TypeScript 7.0.2 在 2026 年 8 月 20 日发布。生产迁移不建议直接写 latest,因为它会让一次版本变更和后续发布混在一起。先固定到经过核对的具体版本,再用锁文件保证本地和 CI 拿到同一份包。
sql
npm install -D typescript@7.0.2
npx tsc --version
版本输出要和 package.json 以及锁文件一致。若项目采用了 6 与 7 的 alias,版本检查要分别执行原生入口和兼容入口,确认命令名没有被包管理器覆盖。版本不一致时,不要继续比较耗时,先清理安装目录并重新安装。
基线测试要保持条件不变。记录当前分支、Node 版本、包管理器版本、锁文件摘要、物理内存和构建命令。完整构建前删除项目引用缓存,再运行同一条命令。推荐把本地和 CI 分开记录,因为本地磁盘和 CI 容器的差异足以掩盖编译器变化。
lua
find . -name "*.tsbuildinfo" -delete
rm -rf node_modules/.cache
time npx tsc -b --noEmit
上面的清理命令是类 Unix shell 写法,Windows 团队可以用 PowerShell 或仓库已有的清理脚本完成同样的事情。重点是清理范围一致,不要一次删除整个依赖目录后又把安装时间算进编译时间。
建议用同一台机器连续测三次,记录每次耗时和内存峰值,再取中位数作为本地基线。CI 则保存一条迁移前构建记录,用同样的任务、缓存策略和依赖锁文件与 7.0 对比。只有条件一致,提速或变慢才有判断价值。
检查四:调节并行参数
TypeScript 7.0 默认会并行处理多个阶段。官方资料说明,类型检查 worker 默认数量为 4。新参数中,checkers 用来调节类型检查 worker,builders 用来调节项目引用构建,singleThreaded 可以关闭并行,方便定位差异。
不要从默认值直接跳到机器核数。线程数增加会争用内存,也会和构建进程、测试进程争用 CPU。可以从保守配置开始,把构建耗时、峰值内存和失败日志一起记录。
css
# 先用保守并行度建立对比
npx tsc -b --checkers 4 --builders 1
# 内存充足且基线稳定后再增加检查 worker
npx tsc -b --checkers 8 --builders 2
# 排查并行相关差异
npx tsc -b --singleThreaded
官方博客还展示过在 8 核机器上提高 checkers 后的进一步收益,但这个结果属于特定代码库和硬件条件。项目自己的选择应该由基准测试决定。若 checkers 增加后速度没有改善,或者内存峰值逼近容器限制,就回到较小的值。若并行模式和单线程模式输出不同,先保存两份完整日志,再用 singleThreaded 作为诊断基线,不要把差异直接归咎于编译器错误。
builders 影响项目引用构建的并发度。单仓库里有很多互相依赖的 package 时,它的影响可能比单个包的类型检查更明显。调参时一次只改变一个维度,先固定 builders 再改变 checkers,结果更容易解释。CI 配置要写死参数,不要让不同 runner 的 CPU 数量隐式决定并行度。
检查五:处理编辑器和单仓库灰度
命令行通过不等于编辑器已经切换。编辑器可能使用工作区的 TypeScript,也可能继续使用自己的语言服务。VS Code、Visual Studio 和其他支持 LSP 的编辑器对 TypeScript 7 的接入方式并不完全相同,团队需要按所用编辑器的官方说明确认语言服务来源。
验收时打开一个确实存在类型错误的文件,确认错误能够出现,再做查找引用、自动补全和重命名等高频操作。不要只看状态栏的版本文字。编辑器体验的目标是缩短反馈,而不是让某个扩展显示一个新版本号。
单仓库灰度要限制影响范围。可以先保留根目录的稳定版本,用独立的 CI job 对一个非关键 workspace 执行 7.0,或使用 npm exec 临时调用固定版本。临时调用适合做基线和验证,不应绕过锁文件成为长期生产方案。
perl
npm exec --package=typescript@7.0.2 -- tsc -b packages/demo/tsconfig.json --noEmit
find . -name "*.tsbuildinfo" -delete
如果试点 package 依赖根目录 hoist 的 TypeScript,必须检查实际解析路径。不同 workspace 看到的版本不一致,会让错误出现在安装阶段而不是构建阶段。试点通过后,再决定是统一升级根依赖,还是继续保留 6 与 7 的过渡布局。
灰度期间可以用下面的表格记录判断依据。
| 灰度对象 | 观察内容 | 通过信号 |
|---|---|---|
| 一个非关键 workspace | 类型错误数量和构建输出 | 输出与稳定版本一致 |
| 一条独立 CI job | 耗时、峰值内存、缓存命中 | 耗时下降且没有资源告警 |
| 一名开发者的编辑器 | 补全、诊断、查找引用 | 语言服务稳定响应 |
| 依赖旧 API 的工具 | 安装、lint、测试和打包 | 继续读取 6.0 兼容层 |
表格只是记录框架,不能替代真实运行。试点出现类型结果变化时,先确认依赖版本、tsconfig、缓存和参数完全一致,再决定是否暂停扩大范围。
检查六:CI 对比和可执行回滚
CI 是迁移的总验收场景。单次构建变快并不够,还要看失败是否更容易诊断、内存是否稳定、缓存是否仍然有效。灰度 job 和主 job 使用同一份代码、同一份锁文件,唯一变化是 TypeScript 入口和并行参数。把两条 job 的日志和产物都保留下来,便于对比类型错误和生成文件。
回滚方案要在发布升级前写好。最小回滚动作是恢复 package.json 中的版本、恢复锁文件、删除 tsbuildinfo,再用原来的 tsc 命令跑一次干净构建。不要只改 package.json 而保留旧锁文件,也不要把不同编译器生成的缓存混在一起。
perl
# 回滚到团队已经验证过的 6.0 版本
npm install -D typescript@6.0.3
find . -name "*.tsbuildinfo" -delete
npm ci
npx tsc -b --noEmit
如果项目采用并行 alias,回滚可以先让生产 job 恢复到 6.0,再保留独立 job 继续观察 7.0。这样不会因为一次试点失败而丢掉全部测试结果。回滚完成的判断是稳定分支重新通过安装、类型检查、单元测试和打包,而不是只看到 npm install 返回成功。
可以把升级决策压缩成几句话。项目只把 TypeScript 当作命令行编译器使用,工具链没有旧 API 依赖,且基线测试已经显示收益,可以进入灰度。项目有直接 API 调用,就保留兼容包并行运行,等依赖完成适配。项目在内存受限的 CI 中收益不明显,就优先调低并行度,不要为了追求官方倍数强行增加 worker。项目的编辑器和构建结果尚未核对,就不要把升级扩散到主干。
TypeScript 7.0 的价值不只是一个更快的数字,而是把大型工程的反馈环路压短。真正稳妥的迁移顺序,是先找 API 依赖,再固定版本和基线,随后调并行参数、做小范围灰度,最后用可重复的 CI 和回滚动作收口。速度提升应当服务于工程稳定性,而不是替代工程验证。
参考来源:
- 微软 TypeScript 官方博客,Announcing TypeScript 7.0:devblogs.microsoft.com/typescript/...
- TypeScript 官方 GitHub release:github.com/microsoft/t...