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 usedInstantiations 这个数字特别能说明问题。我见过一个项目它飙到了几千万,定位下去发现是有人写了个“万能”的表单类型,从一个巨大的字段配置对象里用条件类型反推每个字段的值类型,结果每次用到这个表单,类型系统都要把那一大坨条件类型重新实例化一遍。

更进一步可以让编译器生成 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 成本。真正落地时,先小范围验证,再逐步升级。