在真实的Next.js应用上对TypeScript 7的Go编译器进行性能测试。
在真实的Next.js代码库中,对TypeScript 6与7的tsc检查时间进行实际对比,包括CI匹配错误问题及升级指南。
同样是这412个文件,在两个不同的编译器版本下分别进行了两次检查。在TypeScript 6上,检查耗时3.8秒;而在TypeScript 7上则仅需0.41秒。实际上执行检查的类型检查器在底层是完全相同的。
微软公布的数值是在VS Code环境中测得的8到12倍的加速效果。这里真正重要的数据是tsc --extendedDiagnostics在特定代码库中生成的检测结果。那些因为Prisma生成步骤失败而停滞的CI任务,并不会仅仅因为类型检查器速度变快就整体上快上十倍,只有那个步骤会变得更快而已,流水线中后续的所有步骤依然保持原来的慢速。
打开终端。
跳过next dev命令,直接从应用程序的根目录运行类型检查器:
pnpm exec tsc --noEmit --extendedDiagnostics
注意它输出的Check time数值。在最后之前,这一行数据是此处唯一值得追踪的测量结果。
微软于2026年7月8日发布了TypeScript 7.0。其类型系统本身并未改变,编译器二进制文件仍名为tsc,但实际上它已是该编译器的Go语言版本。1.0版本发布时的表格显示,VS Code的检测时间从125.7秒降到了10.6秒。这一数值针对的是微软自身的代码库,而非普通的Next.js项目。
真正有用的信息是:对于那些已经在其他方面经过性能测试的小型四路由 Next.js 应用,其对应的数值是多少;此外还有另一个数值:当底层检查工具更新后,执行 next build 所需的时间是多少。同样重要的是要记录下当自动化拉取请求将“更快”与“行为不同”视为同义时会出现的错误类型。
下午的 CI 系统在类型错误问题上撒了谎
想象这样一个场景:某个团队在周二将一个账单应用中的 typescript 依赖项升级到了版本 7,因为一篇博客文章声称这样能提升10倍的速度。CI 系统很快就显示为绿色状态。受此鼓舞,一名审查员批准并合并了一个 branded-id 工具函数,但后来发现该函数在同事的本地机器上无法通过类型检查。
该辅助工具能在 CI 环境中正常编译,只是因为 CI 仍通过提升的根工作区依赖来获取 typescript 6 版本,而本地机器则直接安装了 7 版本。类型系统本身并未出现差异——这正是微软所声称的——但 CI 环境中的 tsconfig 将 typescript 固定在了某个包别名 @typescript/typescript6 上,这个别名是预览阶段添加的覆盖项遗留下来的,至今仍未被移除。
因此,真正的问题并非 TypeScript 7 的行为有所不同,而是存在两个不同的编译器二进制文件、不一致的锁文件配置,以及一条声称“7 版已可用”的 Slack 消息,但实际上至少在某些环境中并非如此。
实际应用中的情况如下:有一个名为“去掉as InvoiceId的类型转换,7版本更为严格”的拉取请求。但实际上TypeScript 7并没有变得更严格。同一个提交还启用了erasableSyntaxOnly编译器标志,这完全是另一项独立的策略变更,稍后会单独讨论。将纯粹的性能提升与行为策略变更混为一谈,正是这些误解产生的原因。
正确的做法是将此类提交拆分为两部分:仅处理版本升级,再将策略变更单独处理。只有这样,时间测量才有意义。
微软实际发布的声明
在2026年7月8日发布的官方TypeScript 7.0公告中,Daniel Rosenwasser称此次版本带来了原生代码执行能力、通过共享内存实现的多线程处理功能,以及一系列优化措施,这些优化使得完整构建时的运行速度提升8到12倍。
该公告中大多数人会忽略的部分是关于类型系统保持不变的那句话。
根据公告,新的基于Go的实现是通过精心设计的移植过程从现有实现中延续而来的,并非从头开始重新构建,其类型检查行为在结构上与TypeScript 6.0保持一致。
这次升级并没有引入任何新的语法。变化仅在于相同逻辑下的执行速度提升。如果版本升级后出现类型错误,那并非正常现象——这属于需要上报的漏洞。
Next.js 16.3 添加了相关文档,说明如果项目中直接安装了 TypeScript 7 作为依赖项,next build 将使用 TypeScript 7 进行类型检查。这样一来,除了单独运行 tsc 外,又多了一种测量时间的方法。
我实际使用的两种计时方式
整个测试都使用相同的应用。版本为 Next.js 16.3、React 19,包含四个路由、发票表格;本次测试中关闭了编译器相关选项,以避免不同数据相互干扰。
第一种计时方式:tsc --noEmit --extendedDiagnostics。第二种计时方式:运行 next build,并观察其输出中的类型检查相关行。
我已将 TypeScript 7 添加为项目的依赖项。
pnpm add -D typescript@7
可执行文件的名称仍然是 tsc。在预览阶段,该包的名称为 @typescript/native-preview,其二进制文件的名称则为 tsgo。既然现已进入稳定版本阶段,这种命名方式已被废弃。如果您看到仍有引用 tsgo 的代码示例或讨论帖,那都是针对预览阶段的描述,并非指当前的工具。
为让两个主要版本能够在同一系统中共存,微软发布了配套包 @typescript/typescript6。该包提供了一个 tsc6 二进制文件,这样常规的 tsc 命令就可以指向版本 7,而无需放弃那些仍依赖版本 6 的团队或工具。
pnpm add -D @typescript/typescript6
之后,两个版本都可以使用相同的 tsconfig.json 文件:
pnpm exec tsc6 --noEmit --extendedDiagnostics
pnpm exec tsc --noEmit --extendedDiagnostics
相同的源文件,相同的strict设置,以及使用create-next-app创建项目时Next.js生成的相同路径别名。
每个命令都运行了三次,其中第一次的结果被舍弃,因为基于热文件系统缓存的检测并不能代表真实的初次检查情况。
该代码库中的数值表现
该测试应用包含四个路由,以及大约80个属于项目本身的TypeScript文件,此外还有Next.js自动生成的文件。
使用tsc6运行TypeScript 6.0版本,取两次有效运行结果的中值:
- 检查的文件数:412
- 检查时间:3.82秒
- 总耗时:4.25秒
使用相同的配置,通过tsc运行TypeScript 7.0版本:
- 检查的文件数:412
- 检查时间:0.41秒
具体到类型检查时间,这一数值大约提升了9倍。并非12倍,也远不及人们常提到的VS Code场景中从125秒降至10秒的降幅。这只是该特定代码库的实际表现。
查看next build中的类型检查步骤:
- 版本6时:5.1秒
- 版本7时:1.4秒
next build中的其他操作——包括四个路由的打包与静态文件生成——耗时基本保持不变。如果你的CI流水线依次执行代码检查、类型检查、构建以及端到端测试,那么类型检查部分的耗时会有明显减少,但端到端测试部分的耗时则没有变化。
另一个项目由于使用了大量Zod架构以及约300个用于验证逻辑和处理的文件,其提升幅度更大:
- 版本6的检测时间:11.4秒
- 版本7的检测时间:1.3秒
包含更多类型的代码似乎能带来更大的性能提升幅度。一个只有十几个文件的小型营销网站几乎无法实现10倍的速度提升,因为根本没有足够的内容供检测工具分析。
升级后出现的漏洞
这并非类型检测行为的改变,而是工具方面的问题。
eslint-plugin-react-hooks 仍通过其 parserOptions.project 设置来启动 typescript。在版本 7 下这功能正常,但在早前的 tsgo 预览阶段就已经出问题了。一个过时的 parserOptions 块仍在指向另一个独立的 tsconfig.eslint.json,该文件特意设置了 "compilerOptions": { "strict": false },以避免旧版测试报错。
CI在代码检查时使用的是简化后的配置,而tsc则使用项目实际的配置文件。两者存在不同的标准。因此,在一个测试工具中出现了noImplicitAny设置缺失的情况,而代码检查工具却无法发现。当版本7让tsc的运行成本低到可以持续执行时,我直接在拉取请求的检查步骤中加入了tsc --noEmit选项,并彻底移除了仅针对ESLint的简化版tsconfig配置。
{
"scripts": {
"typecheck": "tsc --noEmit",
"lint": "biome check .",
"ci": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
}
}
实际的修复方案并不复杂。但流传的说法却是“版本7破坏了我们的类型系统”。其实并非如此,真正的问题出在重复的配置文件上。
检查您的系统中实际运行的程序
在相信任何数据之前,先确认是哪个可执行文件正在执行任务。
pnpm exec tsc -v
你在输出结果中查找的是Version 7.x。如果仍显示5或6,说明你的工作区从某个地方调用了过时的版本。在pnpm单仓库结构中,which会指向错误的位置,而pnpm exec则不会。
请分别运行三次诊断程序,丢弃第一次的运行结果。
pnpm exec tsc --noEmit --extendedDiagnostics
该输出中值得关注的字段包括:已处理的文件数量、总代码量中来自库代码的比例、来自类型定义的比例、属于你自己的源代码比例,以及检查耗时和总耗时。
现在在安装7版本的同时也安装6版本,并让它们都指向同一组文件。
pnpm add -D @typescript/typescript6
pnpm exec tsc6 --noEmit --extendedDiagnostics
在拥有数百个文件的代码库中,如果检查耗时没有出现显著的多倍下降,那就说明你实际上并没有使用版本7,或者你的测试样本太小了——十几個文件根本无法反映出任何问题。
另外,建议对每个二进制文件分别运行两次next build,每次只记录类型检查所花费的时间。不要将整个构建耗时都归结为TypeScript的速度问题——Turbopack是另一个负责独立工作的系统。
如果在版本7下会出现而在版本6下不会出现的类型错误,且两者的tsconfig配置完全相同,应将其报告出来。这超出了此处讨论的范围。微软自己也表示检查逻辑在结构上并未改变,因此这类差异属于缺陷,而非正常的迁移成本。
速度提升的真正来源
性能提升体现在检查器本身。解析和绑定操作也变快了,但真正反映在 CI 日志中且人们能切实感受到的还是检查耗时。
编辑器的响应速度则是另一回事——它与语言服务相关,而非独立的编译器。在发票表格中,诸如跳转到定义之类的导航操作主观上感觉更快了。由于没有记录按键级的响应时间,因此这里无法提供毫秒级的数据表。
next dev 及其快速刷新功能并未因这一变更而显著提速,因为从一开始,快速刷新就从未在完整的 tsc 运行过程中被阻塞过。
这种做法真正发挥作用的地方在于那些在处理每个文件后都会触发 tsc --noEmit 命令的脚本或自动循环。这样的循环执行速度足够快,以至于跳过检查已不再是一种诱人的捷径。这才是真正存在但未被明说的好处。那些仍然使用普通枚举而非 as const 对象的脚本,现在也能更快地得知这一变化。
统计实际成本
安装方面:只需升级一个依赖项,同时删除不再需要的遗留 tsgo 脚本。
CI流程的影响:在主应用中,类型检查步骤的时间从3.8秒缩短到0.4秒;在worker树中,则从11.4秒降至1.3秒。流水线中其余的八分钟多时间则未受影响。
谣言带来的代价:有个拉取请求将某次功能退化归咎于版本7,而实际上这是由同一次提交中包含的某个标志位变更导致的。应将这类提交分开处理。
编辑器体验:有所改善,但还不至于需要在评论中用数字来量化。
命名规则:tsc现在代表版本7,而tsc6则是备用选项。如果这两个二进制文件都在系统的PATH路径中,应在README中明确说明,以免日后造成混淆。
应该升级到7版本还是继续使用6版本?
建议升级到版本7。类型检查的功能机制不变,只是运行速度更快——而且无需学习新的语法。
只有当存在某个特定插件、且你能明确知道其名称,同时该插件尚未为版本7添加支持时,才应选择版本6。请将该插件的名称直接写入版本锁定值中。“等待相关功能稳定”这一理由本身并不成立。
不要在同一个拉取请求中将版本7升级与erasableSyntaxOnly更改一起处理——一旦出现故障,你将无法判断是哪项更改导致了问题。
也请不要仅仅因为版本7能让tsc --noEmit运行得更快,就将其从CI流程中移除。速度正是保留该检查项的理由,而非删除它的理由。
此对比的客观局限
从3.82秒降至0.41秒的情况仅适用于这款特定的四路由应用;而从11.4秒降至1.3秒则来自另一个以工作进程为核心的代码库。微软所声称的8到12倍的性能提升是指在类似VS Code规模的代码仓库中进行的完整构建测试,这里并未重新运行过VS Code。
“结构完全相同”这一表述出自2026年7月8日的微软官方公告。如果项目在升级后确实出现了错误变化,应将其视为需要上报的缺陷,而非可以忽视的偶然副作用。
目前无法从这里查看依赖项的加载情况。如果本地执行pnpm exec tsc -v显示的版本号与CI日志中的版本号不同,那就说明你实际上并未验证过TypeScript 7——这不过是路径解析问题伪装成的版本比较而已。
分别运行 tsc6 和 tsc 各三次。记录每次运行时的检查时间与文件数量。这四个数值就是原始数据集,若希望获得针对您特定配置的反馈,可将其分享出去。
用于测试文件夹的最简复现方案
如果您暂时不想修改实际应用程序,以下是仍能体现“提升问题”的最小安装组合。
mkdir ts7-lab && cd ts7-lab
pnpm init
pnpm add -D typescript@7 @typescript/typescript6
echo '{ "compilerOptions": { "strict": true, "noEmit": true } }' > tsconfig.json
echo 'export type InvoiceId = string; export const n: InvoiceId = "inv_1";' > index.ts
pnpm exec tsc -v
pnpm exec tsc6 -v
pnpm exec tsc --extendedDiagnostics
pnpm exec tsc6 --extendedDiagnostics
记下两个版本的字符串以及各自的检查时间。接着添加一个工作区包,该包仍将 typescript@6 作为依赖项,然后查看从仓库根目录执行 pnpm exec tsc -v 所输出的结果。这种不一致正是持续集成系统可能给您的意外情况。
在发票项目中,另一件值得记录的是next build是否真的会输出来自版本7的Finished TypeScript提示行。如果该提示行从未出现,说明Next.js运行的类型检查工具与typecheck脚本调用的工具是不同的。需要让这两者保持一致——正是由于同时运行了两种不同的检查工具,才导致了之前branded-id漏洞未能被及时发现。
给审核人员的一个一分钟快速验证方法:打开app/invoices/page.tsx,将鼠标悬停在searchParams类型上,等待提示框出现。在版本6和版本7上都重复此操作。这个验证不需要计时器——关键只是查看悬停提示文本在两个版本之间是否保持不变。功能表现相同,只是底层引擎有所不同而已。
如果你的项目有单独的、设置了宽松规则的 tsconfig.eslint.json 文件,那么在升级的同一周就将其删除。一个简单的类型检查工具足以消除 lint 需要使用与构建时不同的规则进行检测的借口。
从第一天起就应该记录的一个指标是:在升级前后分别执行 tsc --noEmit --pretty false 2>&1 然后传递给 wc -l,统计错误数量。两次的错误数必须完全一致。在 invoices 应用中两次都是零;在 worker-tree 项目中则是四次,且两次的错误文件和信息完全相同。这种一致性其实就是整个迁移过程的体现。如果两次的错误数不一致,就别再重复那些夸张的宣传语了,直接对比两份输出日志的差异吧。
在每次升级后,将两份日志分别保存为 /tmp/tsc6.txt 和 /tmp/tsc7.txt,保留约一周时间。待一切稳定后再删除它们——但不要在正式发布升级版本的当晚删除。
给后续接手该代码库的人的指导
在拉取请求模板中要求提供 pnpm exec tsc -v 的输出结果。如果显示的版本号不是7,说明性能提升实际上并未被应用。
不要在同一个变更中同时进行此次升级与 erasableSyntaxOnly、verbatimModuleSyntax 或更全面的 tsconfig 清理操作。这些属于后续步骤,当前此次升级仅旨在让编译器以更快的速度完成相同工作。
尽管现在运行 tsc --noEmit 几乎不耗费资源,但仍应在持续集成流程中保持其运行状态。正是这种极低的成本,才使得继续保留它成为合理选择。
现场录制的实验环节:命令与输出结果
本部分记录了本系列中始终使用的同一套四路由发票测试环境。开始之前确定的版本为:Node 24、TypeScript 7、Next 16.3。
这些步骤保存在仓库的notes/lab.md文件中,以便后续操作不会依赖记忆。你可以按顺序复制这些内容。
node -v
pnpm exec tsc -v
pnpm exec next --version
在笔记的顶部写下所有三个版本号。如果有任何主要版本与你认为正在使用的版本不一致,请立即停止——之后的所有操作都会以更隐蔽的方式产生误导性的结果。
接下来是路由遍历步骤:
pnpm exec next dev
访问 /、/invoices、/invoices/1、/settings,然后再访问一次 /invoices。在开发者工具中开启“保留日志”功能。截取筛选框和地址栏的屏幕截图。事实证明,这样的组合在更多测试中能提供比预期更有用的数据。
然后运行类型检查器:
pnpm exec tsc --noEmit --pretty false
echo $?
返回代码为零并非最终结果——它只是继续检查运行时行为的信号而已。
最后,也是本文真正要讲的内容:执行“如何在您的机器上查看”部分中列出的命令。不要因为已经在这里看到了数值就跳过它们——这些数值并非来自您的机器。环境温度、16GB的笔记本电脑,以及Chrome在后台运行的各种任务,对RSS值、检查时间以及获取数据失败的时序的影响,远远超过框架版本升级所带来的影响。
还有一个值得保持的习惯:在记录中只写一行“修复失败”的说明——一句话即可,比如“尝试了X方法,但仍然出现Y问题”。正是这样的记录才使它成为有效的实验记录,而非经过润色的演示文稿。有用的补充信息应包括版本列表、具体命令、输出结果以及修复失败的尝试记录,而仪表板截图则不算。
相关阅读
- TypeScript的Go编译器与原生执行:迁移指南 — 了解基于Go的TypeScript编译器及Node.js原生执行方式将如何影响React、Next.js项目,以及当前应在tsconfig中做哪些调整。
- TypeScript 7的Go重写机制:无需修改代码即可提升速度 — 了解TypeScript 7基于Go的编译器如何实现8到12倍更快的构建速度,这种架构变更的原理是什么,以及如何安全地升级现有项目。