TypeScript 7 Beta 用 Go 重写之后:我更关心反馈链路有没有变短
TypeScript 项目越大,类型检查越容易成为开发体验的瓶颈。
你改一行代码,编辑器要重新分析类型;提交前跑 tsc,CI 也要等类型检查完成。项目规模上来后,这些等待时间会越来越明显。
2026-04 的 TypeScript 7.0 Beta 把 Go 原生编译器这件事推到了更适合试用的位置。它的核心目标很明确:提升大型项目里的类型检查、编辑器反馈和构建相关性能。
我关注这件事,不是因为“Go 重写”这个标签本身有多新,而是因为它碰到了大型前端项目里一个很真实的痛点:反馈链路变慢。
一个中后台项目做到后期,慢的往往不只是构建。你打开编辑器等类型服务稳定,保存文件等诊断刷新,提交前等 tsc --noEmit,CI 里等类型检查通过。这些等待单次看不夸张,但每天反复出现,会把开发节奏拖得很碎。
我印象很深的一次,是接手一个跑了三年的后台项目。tsc --noEmit 全量一遍要快两分钟,VS Code 打开项目后要等十几秒类型服务才不卡,跳转定义偶尔还会转圈半天。最离谱的是在某个超大的表单页面里,敲一个属性名,自动补全要等一两秒才弹出来——慢到我宁可去翻接口文档手敲,也懒得等编辑器。那阵子我一度怀疑是机器不行,换了台 M 系列的 Mac,编辑器卡顿确实缓解了一点,但 CI 上的类型检查还是该慢慢,毕竟那跟我本地 CPU 没关系。
折腾一圈我才想清楚,这类慢是结构性的,不是堆硬件能根治的。它跟项目里有多少文件、类型写得有多绕、依赖关系有多复杂直接挂钩,而 TypeScript 的类型检查本质上是单线程跑在 JS 引擎上的,规模一上来就容易触到天花板。这也是为什么 TypeScript 7 Beta 出来后,我会专门多看两眼。
为什么类型检查会慢
TypeScript 不只是把 TS 转成 JS。
它还要做很多分析:
- 解析源码
- 建立类型关系
- 推断泛型
- 检查模块依赖
- 生成诊断信息
- 给编辑器提供智能提示
大型项目里,文件数量、类型复杂度和依赖关系都会放大这些成本。
我见过最明显的情况,是一个后台 monorepo 里有几十个包,组件库、业务页面、接口类型和工具函数互相引用。单个文件看起来不复杂,但类型系统要跨文件追踪泛型、条件类型、联合类型和模块导出。你只是改了一个表格列配置,编辑器可能要重新分析一串依赖链。
类型检查慢还有一个隐蔽原因:类型写得太“聪明”。例如过深的条件类型、递归类型、自动推导一切的工具类型,短期看很优雅,长期可能让编译器和人都吃力。
1type DeepReadonly<T> = { 2 readonly [K in keyof T]: T[K] extends object 3 ? DeepReadonly<T[K]> 4 : T[K]; 5};
这种类型本身没问题,但如果在大型数据结构、组件 props、接口返回值里层层套用,就会不断放大检查成本。TypeScript 性能问题不只来自编译器,也来自项目自身的类型设计。
我自己排查这类问题的习惯,是先打开 extendedDiagnostics,让编译器把时间都花哪了说清楚:
1npx tsc --noEmit --extendedDiagnostics
输出里我最关心几行:Check time(类型检查耗时)、Instantiations(类型实例化次数)、Memory used。Instantiations 这个数字特别能说明问题。我见过一个项目它飙到了几千万,定位下去发现是有人写了个“万能”的表单类型,从一个巨大的字段配置对象里用条件类型反推每个字段的值类型,结果每次用到这个表单,类型系统都要把那一大坨条件类型重新实例化一遍。
更进一步可以让编译器生成 trace,丢进 Chrome 的 chrome://tracing 或者 https://ui.perfetto.dev 里看火焰图:
1npx tsc --noEmit --generateTrace ./trace-output
火焰图里哪个 checkExpression、哪个类型的实例化占了大块时间,一眼就能看出来。我靠这招定位过一个 node_modules 里第三方库的类型把我们整个项目拖慢的情况——不是我们自己的代码烂,是依赖的类型设计有问题,最后用 skipLibCheck 临时绕过去了。这些诊断手段在 TS 5.x 上就有,跟会不会升 7 没关系,是任何大项目都该会用的基本功。
Go 重写意味着什么
Go 版本编译器的目标是更快地完成类型分析和编译相关工作。
更快的 TypeScript 编译器,会影响这些场景:
- 本地类型检查
- 编辑器响应速度
- CI 构建时间
- monorepo 大项目检查
- 增量构建体验
这不是语法层面的小更新,而是底层执行效率的变化。
这件事从 2025 年开始被前端圈反复讨论,到 2026 年 Beta 出来后才更适合拿到真实项目里做评估。原因很简单:TypeScript 已经不只是“给 JavaScript 加类型”的语言,而是前端工程的基础设施。编辑器提示、重构、跳转定义、CI 校验、库的类型发布,背后都依赖同一套类型分析能力。
如果编译器速度提升,收益不会只体现在 tsc 命令少等几秒,还会体现在更短的反馈路径上。大型项目里,开发者最痛苦的往往不是一次完整构建,而是每次保存之后提示延迟、每次切分支之后类型服务卡住、每次 CI 排队时只能等类型检查结束。
不过,重写编译器也意味着需要特别关注一致性。对业务项目来说,速度提升当然重要,但更重要的是同一段代码在新旧编译器下是否给出一致的诊断、是否生成一致的声明文件、是否和现有构建工具保持一致行为。
我会先量反馈链路,而不是只看跑分
如果要在项目里验证 TypeScript 7 Beta,我不会只跑一次 tsc。
我会把反馈链路拆成几段:
1打开项目 -> 类型服务稳定 -> 修改文件 -> 诊断刷新 -> 本地检查 -> CI 检查
然后分别记录时间。
1time npx tsc --noEmit 2time npx tsc --build --clean 3time npx tsc --build
如果是 monorepo,还要分包看:
1time npx tsc -p packages/ui/tsconfig.json --noEmit 2time npx tsc -p apps/admin/tsconfig.json --noEmit
因为很多时候,全量检查变快不代表日常开发最痛的地方变快。一个业务同学感受到的是编辑器是否卡、保存后错误是否及时、CI 是否少排队,而不是技术文章里的峰值跑分。
对业务项目有什么价值
对小项目来说,差异可能没那么明显。
但对大型前端项目,类型检查速度提升会直接减少等待。
尤其是组件库、后台系统、monorepo、多包工程,TypeScript 性能优化会让开发和 CI 都更顺。
一个可以量化的做法,是在升级前先记录几组基线:
1time npx tsc --noEmit 2time npx tsc --build --clean 3time npx tsc --build
如果项目用了 project references,还要分别看冷启动和增量构建。很多团队只看一次全量构建时间,但日常开发里更常见的是增量检查。全量快不代表编辑器体验一定快,CI 快也不代表本地反馈一定快。
我比较建议把指标拆成四类:
- 本地
tsc --noEmit时间 - 编辑器打开项目后的类型服务稳定时间
- CI 类型检查时间
- 类型错误数量和错误文本差异
前面三项看性能,最后一项看风险。
我还会加一个“错误差异表”。比如同一份代码,新旧版本分别跑类型检查,把新增错误、消失错误、错误文案变化记录下来。
1新增错误:需要判断是真问题,还是诊断策略变化 2消失错误:需要确认是不是旧版本误报,还是新版本漏报 3声明差异:库项目要重点检查 .d.ts 输出
这一步很烦,但很必要。编译器升级不是普通依赖升级,它会改变团队理解代码正确性的方式。
升级仍然要谨慎
编译器重写是大工程,早期版本需要认真验证。
升级前建议检查:
- 类型检查结果是否一致
- 构建工具是否支持
- ESLint 和编辑器插件是否兼容
- CI 时间是否真的下降
- 是否出现新的诊断差异
性能提升很重要,但稳定性仍然是第一位。
特别是组件库和 SDK 项目,还要看声明文件输出。业务应用只要自己能编译通过就行,但库项目的 .d.ts 会被下游消费。一次看似无害的诊断差异,可能影响别人安装后的类型推导。
还有 ESLint、tsserver 插件、路径别名、框架编译器这些周边工具。很多前端项目并不是直接裸跑 tsc,而是通过 Next.js、Vite、tsup、rollup、babel、swc 等链路间接消费 TypeScript。升级时要确认整个链路都支持,而不是只看 TypeScript 本身。
类型设计也要做性能预算
编译器变快是一方面,项目自己的类型设计也要收敛。
我现在会特别警惕几类类型:
- 递归很深的工具类型
- 嵌套很多层的条件类型
- 从巨大配置对象里反推所有业务类型
- 为了少写接口而过度依赖自动推导
这些类型有时很漂亮,但错误信息不友好,编译成本也高。更麻烦的是,团队新人很难判断它们到底在表达业务,还是在展示类型技巧。
在复杂中后台里,我更愿意把关键的数据结构写得直白一点:
1interface TableColumn { 2 key: string; 3 title: string; 4 width?: number; 5 visible?: boolean; 6} 7 8interface QueryFormValues { 9 keyword: string; 10 status: "all" | "enabled" | "disabled"; 11 page: number; 12}
这类类型不炫,但稳定、可读、错误也好修。TypeScript 性能优化不应该只等编译器救场,业务代码也要少制造不必要的类型负担。
我对 TypeScript 性能优化的个人感受
我现在越来越觉得,TypeScript 性能不是“工具组的事”,也是业务代码作者的事。编译器变快当然好,但我们也可以少写一些过度复杂的类型。类型系统应该帮团队把字段该有什么、取值能是什么说清楚,而不是变成另一个需要猜的运行时。
例如组件 props 类型,能明确写接口就不一定要从一堆配置里自动推导:
1interface ButtonProps { 2 variant: "primary" | "secondary"; 3 size: "sm" | "md" | "lg"; 4 disabled?: boolean; 5}
这类类型不酷,但稳定、可读、错误信息也友好。大型项目里,可维护性经常比类型技巧更重要。
延伸阅读
推荐关注 TypeScript 官方博客、Anders Hejlsberg 相关演讲、TypeScript Performance Wiki,以及大型 monorepo 工程里关于 project references 的实践文章。它们能帮助你从语言特性之外理解 TypeScript:它已经是前端工程反馈速度的一部分。
TypeScript 7 Beta 用 Go 重写这件事,说明 TypeScript 已经从语言工具走向大型工程基础设施。
对前端团队来说,这类变化值得关注,因为它会直接影响开发反馈速度和 CI 成本。真正落地时,先小范围验证,再逐步升级。