TypeScript 7.0 编译器用 Go 重写,速度暴增10倍——背后到底做了什么?

这是 TypeScript 14 年来最大的一次变革。不是语法变了,不是 API 变了------是编译器本身被换掉了。


一、125 秒 → 10 秒,这 10 倍速从哪来的?

先看一组数据。

2026 年 7 月 9 日,微软正式发布 TypeScript 7.0。官方博客放出了一组基准测试:

代码库 代码量 TS 6 构建时间 TS 7 构建时间 提速倍数
VS Code ~230 万行 125.7 秒 10.6 秒 11.9x
Sentry 大型项目 139.8 秒 15.7 秒 8.9x
Bluesky 中型项目 24.3 秒 2.8 秒 8.7x
Playwright 中型项目 12.8 秒 1.47 秒 8.7x
tldraw 小型项目 11.2 秒 1.46 秒 7.7x

VS Code 那个数字最夸张------230 万行代码,原来编译一次要两分多钟,现在 10 秒出头。

简单算一笔账:假设你每天完整编译 10 次,TS 6 时代要等 21 分钟,TS 7 只要不到 2 分钟。省下来的 19 分钟,够写一个功能了。

但问题来了------这 10 倍速到底从哪来的?

不是优化了算法,不是换了数据结构,不是调了参数。

答案是:**微软把整个 TypeScript 编译器从 TypeScript/JavaScript 重写成了 Go。

那这是为什么呢?


二、TS 6 的困境:用 TypeScript 编译 TypeScript,问题在哪?

2.1 自举编译器 tsc

TypeScript 编译器(tsc)一直是用 TypeScript 自己写的。这在编程语言领域叫"自举"------编译器能编译自己。

GCC、Rust、Go、Swift 的编译器都是自举的。好处很明显: dogfooding(自己吃自己的狗粮),语言设计的问题会第一时间在编译器开发中暴露出来。

但是问题也很明显------TypeScript 编译器跑在 JavaScript 运行时(V8/Node.js)上,而 JavaScript 运行时天生有几个硬伤。

2.2 单线程:JavaScript 的天花板

JavaScript 是单线程语言。这意味着 tsc 在做类型检查的时候,只能一个文件一个文件地串行处理。

类型检查这件事,本质上是可以并行的------a.tsb.ts 之间的类型检查大多互不依赖,理论上可以同时进行。但在 JS 单线程模型下,你没法高效地并行处理。

可能会有疑问哈,Web Worker 不是可以并行吗?

确实可以,但 Worker 线程之间不能共享内存 。每个 Worker 有自己独立的内存空间,线程间通信只能靠消息传递(postMessage)。对于编译器这种需要频繁访问共享 AST(抽象语法树)和数据表的场景,消息传递的开销太大了------序列化、反序列化、复制数据,光是这些开销就可能抵消并行带来的收益。

意思就是JS 能并行,但并行得不痛快。

2.3 GC 停顿:V8 的垃圾回收会卡住编译流程

V8 引擎的垃圾回收(GC)机制在运行 TS 编译器时也会触发。编译大型项目时,AST 节点动辄上百万个,GC 压力很大。GC 一旦触发 stop-the-world,整个编译流程就得停下来等。

这些停顿单次可能只有几毫秒到几十毫秒,但在一个持续几十秒甚至上百秒的编译过程中,GC 停顿累积起来,对性能的影响不小。

2.4 增量构建的困境:--watch 模式的性能瓶颈

--watch 模式是日常开发用得最多的功能。文件改了,编译器增量更新,只重新检查受影响的部分。

TS 团队在 5.x 版本做了大量优化--------build 模式改进、isolatedDeclarations、声明文件生成优化等等。但这些优化都是在 JavaScript 运行时的「天花板」下面做的修补。

举个例子:--watch 模式需要监听文件变化。TS 6 用的方案是基于轮询(polling)的文件监听------定时去查每个文件的修改时间。文件一多,轮询本身的开销就不小。VS Code 倒是用了一个叫 @parcel/watcher 的 C++ 原生模块来做文件监听,但 TS 编译器自己没有这个能力。

这些限制不是 TS 团队能力不够,是 JavaScript 运行时的天生约束。 只要把编译器放在 JS 运行时上,性能上限就被锁死了。


三、那为什么是 Go呢?

3.1 候选语言:Go / Rust / Zig

微软宣布用 Go 重写编译器后,社区里讨论最多的一个问题是------为什么不是 Rust?

毕竟,前端工具链用 Rust 重写的先例不少:esbuild(Go 写的,但常被和 Rust 一起讨论)、SWC(Rust)、Rolldown(Rust)、Turbopack(Rust)、Oxc(Rust)。Rust 在前端工具链领域已经有成熟的生态。

具体到 TypeScript 编译器的场景,Go 和 Rust 各有优势:

维度 Go Rust
并发模型 goroutine(超轻量,百万级) async/await + 线程
共享内存 支持(且简单安全) 支持(但需要 unsafe 或原子操作)
学习曲线 低(语法简单,上手快) 高(所有权、生命周期、借用检查)
编译速度 极快 慢(大型项目编译时间长)
运行速度 更快(零成本抽象)
GC 有(并发 GC,停顿极短) 无(手动管理,零运行时开销)
生态成熟度 高(云原生标配) 中(前端工具链领域增长快)

TypeScript 团队需要的是什么?

大量并行 + 快速迭代 + 足够好的性能 + 团队能快速上手。

这几个条件一摆,Go 的优势就出来了:

  • goroutine 超轻量,可以轻松开几千个并发任务做文件解析和类型检查
  • 共享内存 并行在 Go 里是原生支持的,不需要像 Rust 那样跟所有权系统搏斗
  • Go 的编译速度极快------这对一个需要频繁迭代编译器的团队来说很重要
  • 学习曲线低------TS 团队是 JS/TS 专家,学 Go 比学 Rust 容易得多

Rust 在绝对运行速度上更快,但 TypeScript 编译器的瓶颈不是单线程速度------而是能不能高效并行。Go 的 goroutine + 共享内存模型刚好对症下药。

这里需要说明一点:以上是技术社区的分析和讨论,微软官方没有公开过详细的选型对比文档。具体的决策过程可能还涉及团队能力、项目周期、技术战略等非技术因素,这些我无法确认。但从技术匹配度来看,Go 确实是最合理的选择。

3.2 Go 提供了什么 JS 给不了的?

三个核心能力:

① 共享内存并行

Go 的 goroutine 可以直接访问共享内存。多个 goroutine 可以同时读写同一块 AST 内存,通过 sync.Mutex 或 channel 做同步控制。

JS 的 Worker 线程做不到这一点------每个 Worker 有独立的内存空间,数据共享只能靠拷贝。

对于编译器来说,这是天壤之别。类型检查需要频繁访问全局类型表、符号表、AST 节点------如果这些数据可以共享访问,并行效率会高得多。

② 原生编译

Go 编译出来的是原生机器码,直接跑在 CPU 上。不需要 V8 的 JIT(即时编译)预热,也没有解释执行的开销。

编译器是一个 CPU 密集型程序------解析语法树、类型推导、生成代码,全是计算。原生编译在这类场景下比 JIT 有天然优势。

③ GC 停顿极短

Go 有 GC,但它的 GC 是并发的(concurrent GC),设计目标就是低停顿。Go 的 GC 停顿通常在微秒级别,远低于 V8 的毫秒级停顿。

在编译大型项目时,Go 的 GC 几乎不会对编译流程造成可感知的停顿。虽然我不能确认官方是否明确说过「无 GC 停顿」,但从 Go GC 的设计原理来看,停顿时间确实短到可以忽略。


四、Go 编译器的三板斧:并行、共享内存、原生速度

4.1 解析和发射:天然并行

编译器的第一阶段是「解析」(parsing)------把源代码文本解析成 AST。第二阶段是「发射」(emission)------把编译结果生成 .js 文件和 .d.ts 文件。

这两个阶段有一个特点:文件之间天然无依赖。

a.ts 的解析不需要等 b.tsb.ts 的解析也不需要等 c.ts。在 TS 6 里,这些文件只能排队一个一个处理。在 TS 7 里,Go 的 goroutine 可以同时解析几十个文件。

发射也是同理。每个文件的代码生成是独立的,直接多线程并行。

这部分并行的加速效果最明显------文件越多,优势越大。这也是为什么 VS Code(230 万行)的提速倍数(11.9x)比 tldraw(小型项目)的提速倍数(7.7x)更大。

4.2 类型检查器:共享内存并行

类型检查是编译器中最复杂的部分,也是 TS 6 性能瓶颈的核心。

类型检查需要全局信息------你得知道某个变量的类型定义在哪,某个接口继承了哪些接口,某个泛型约束是什么。这意味着类型检查不能像解析那样简单地按文件并行。

TS 7 的做法是:用共享内存让多个 worker 同时检查不同文件的类型,共享同一份全局类型表和符号表。

css 复制代码
                    ┌─────────────┐
                    │  共享类型表  │
                    │  符号表      │
                    └──────┬──────┘
                           │
           ┌───────────────┼───────────────┐
           │               │               │
    ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
    │  Worker 1   │ │  Worker 2   │ │  Worker 3   │
    │  检查 a.ts  │ │  检查 b.ts  │ │  检查 c.ts  │
    └─────────────┘ └─────────────┘ └─────────────┘

TS 6 时代,这些文件只能排队。TS 7 时代,多个 worker 并行处理,共享同一份类型数据------不需要拷贝,不需要消息传递,直接读。

有个细节值得注意:根据思否的深度解读文章提到,TS 7 提供了 --checkers 标志来调节类型检查器的并行 worker 数量。这意味着你可以根据机器配置来调整并行度------CPU 核心多的机器可以开更多 worker,进一步加速。

⚠️ 需要说明:--checkers 标志的信息来源于第三方技术文章,我在官方博客中没有找到详细说明。实际使用时建议查阅官方文档确认。

4.3 项目引用构建器并行化:monorepo 福音

如果你的项目是 monorepo 结构(多个子项目互相引用),TS 6 的 --build 模式会按依赖关系串行构建每个子项目。

TS 7 支持项目引用的并行构建------没有依赖关系的子项目可以同时构建。对于大型 monorepo(比如有 10+ 个 package),这意味着构建时间可以从「串行 N 个项目」变成「并行 N 个项目」。

思否文章提到有个 --builders 标志可以调节并行构建器数量。同样,这个信息来源于第三方文章,建议以官方文档为准。

4.4 --watch 模式的革命:把 Parcel watcher 从 C++ 移植到 Go

这是我觉得整个 TS 7 工程中最「疯狂」的一个决定。

前面说了,TS 6 的 --watch 模式用的是轮询方案------定时去查每个文件的修改时间。文件一多,轮询开销就大。

VS Code 倒是有一个高效的方案:@parcel/watcher,一个用 C++ 写的原生文件监听模块,利用操作系统的 inotify(Linux)/FSEvents(macOS)/ReadDirectoryChangesW(Windows)API,可以做到文件变化时即时通知,不需要轮询。

但 TS 编译器不想依赖 C++ 工具链------引入 C++ 意味着跨平台编译、原生模块安装、node-gyp 构建等一堆麻烦事。

TS 团队做了什么?他们把 Parcel watcher 从 C++ 移植到了 Go。

整个 C++ 代码库,重写成 Go,只保留了极少量的汇编 shim(用于跨平台文件名变更检测的特殊处理)。移植版通过了原有的测试套件,并进一步「Go 化」------从直译逐步重构为更符合 Go 习惯的实现。

Parcel 的作者 Devon Govett 在 TS 7.0 的官方致谢中被特别提及。这是一个跨越 C++ → Go → TypeScript 的生态级反馈循环------Parcel 最初服务于前端构建工具,现在它的核心能力被移植到了 Go,反过来服务 TypeScript 编译器本身。

这个决策的影响是:TS 7 的 --watch 模式不再轮询,文件变化通知是即时的。 改一个文件,编译器立刻知道,立刻增量更新。日常开发中 --watch 模式的响应速度会有质的飞跃。


五、性能基准:5 个真实项目的数据

再仔细看这组数据,有几个值得注意的点:

1. 提速倍数和代码量正相关

VS Code(230 万行)提速 11.9 倍,tldraw(小型项目)提速 7.7 倍。代码量越大,并行的收益越高。这说明 TS 7 的并行策略在大型项目上效果更显著------你的项目越大,升级 TS 7 的收益越高。

2. 不是所有环节都快 10 倍

官方说的「10 倍速」指的是完整构建(tsc 命令从头到尾跑一遍)的速度。这包括了解析、类型检查、代码生成等所有环节。其中解析和代码生成的并行收益最大,类型检查因为需要全局信息,并行收益相对小一些。

日常开发中体感最明显的可能不是完整构建速度------毕竟你不一定每天 tsc 10 次。更明显的是 VS Code 里的编辑器响应速度:代码补全、跳转定义、错误提示,这些都依赖 TypeScript Language Server(LSP)。TS 7 的 LSP 也是 Go 实现的,多线程并发响应请求,编辑器提示的延迟会大幅降低。

3. 内存使用

根据什么值得买报道,Slack 团队反馈 TS 7 上线后 CI 上的类型检查时间大幅缩短。虽然我无法确认具体的内存对比数据,但 Go 运行时通常比 V8 在内存管理上更高效,这对大型项目来说也是个好消息。


六、升级指南:从 5.x / 6.x 到 7.0

6.1 安装

bash 复制代码
npm install -D typescript@7

安装后,tsc 命令就是 Go 编译器了。不需要额外配置。

6.2 配置变更清单

TS 7 继承了 6.0 的新默认值,并对一些废弃配置项做了硬错误处理。如果你从 5.x 直接升到 7.0,配置冲击会比较大。

新默认值:

配置项 旧默认值 TS 7 默认值 影响
strict false true 开启所有严格类型检查
module CommonJS esnext 输出 ESM 模块
target ES3/ES5 当前稳定 ECMAScript 输出更现代的 JS
noUncheckedSideEffectImports false true 检查副作用导入
libReplacement true false 禁用 lib 替换
stableTypeOrdering - true(不可关闭) 类型排序稳定化
rootDir 自动推断 ./(需显式设置) 必须显式指定源目录
types 自动加载所有 @types/* \[\](空列表) 必须显式声明

其中 rootDirtypes 是最容易踩坑的变更

types 从「自动加载所有 @types/*」变成了「空列表」。这意味着原来隐式可用的全局声明(如 nodejestbun)现在需要显式声明:

jsonc 复制代码
// tsconfig.json
{
  "compilerOptions": {
    "types": ["node", "jest"]  // 🆕 以前不写也能用,现在必须显式声明
  }
}

rootDir 也需要显式设置:

jsonc 复制代码
// 如果 tsconfig.json 在项目根目录而不是 src/ 内
{
  "compilerOptions": {
    "rootDir": "./src"  // 🆕 以前自动推断,现在必须显式指定
  },
  "include": ["./src"]
}

已变为硬错误的废弃配置项:

废弃项 说明
target: "es5" 完全移除,不再支持 ES5 降级
downlevelIteration 移除(因为 ES5 target 没了)

如果你项目里有 target: "es5",升级时会直接报错,需要改成 es2015 或更高。

6.3 升级建议

markdown 复制代码
升级步骤:
1. 先跑 npx tsc@7 --noEmit 看看报多少错
2. 修复 tsconfig.json 中的配置变更
3. 检查 types 数组是否包含你需要的全局声明
4. 检查 rootDir 是否正确设置
5. 跑一次完整构建,对比速度

配置变更这块不算大问题------改几行 tsconfig.json 就行。真正值得期待的是改完之后的速度提升,那才是实实在在的体验改善。


七、这对开发者意味着什么?

日常开发

最直接的感受:VS Code 里的 TypeScript 提示变快了。

代码补全、跳转定义、错误提示、find-all-references------这些操作都依赖 TypeScript Language Server。TS 7 的 LSP 是 Go 实现的,多线程并发响应,延迟大幅降低。

大型项目打开 VS Code 后「等待 TypeScript 加载」的时间也会缩短。以前可能要等十几秒甚至几十秒红波浪线才出来,现在应该快得多。

CI/CD

完整构建时间大幅缩短。对于有 TypeScript 类型检查步骤的 CI pipeline,构建时间可能从几分钟降到几十秒。这不仅节省等待时间,还能降低 CI 资源消耗(尤其按分钟计费的 CI 服务)。

Monorepo

项目引用并行构建是 monorepo 的福音。如果你有 10 个 package 互相引用,TS 6 会按依赖关系串行构建,TS 7 可以并行构建没有依赖关系的 package。

AI 编程

这点容易被忽略------AI 编程工具(Cursor、Copilot 等)在后台会频繁调用 TypeScript 类型信息来理解代码上下文。TS 7 的更快响应意味着 AI 工具的上下文获取更快,辅助体验也会跟着提升。


八、观察

我第一反应是------前端工具链的「Rust 化」趋势可能要被 Go 劈开一道口子了。

过去几年,前端工具链用 Rust 重写的项目一个接一个:SWC、Rolldown、Turbopack、Oxc......Rust 几乎成了「前端工具性能优化」的默认答案。但 TypeScript 编译器选了 Go,说明 Rust 并不是所有场景的最优解。

核心区别在哪?

Rust 适合「单线程极致性能」的场景Go 适合「大规模并行 + 快速迭代」的场景

TypeScript 编译器的瓶颈是并行能力,不是单线程速度。这不是说 Rust 不好,而是不同的工具适合不同的活,所以 Go 比 Rust 更合适。

Rust 在绝对性能上肯定比 Go 快。但 TS 团队需要的是「10 倍速」而不是「12 倍速」。Go 能用更短的开发周期、更低的学习成本达成目标,为什么不用 Rust 的「理论最优」去换 Go 的「工程最优」?

日常开发或者工作开发中选型也是一样的思考,不是选最强的,是选最合适的。


九、相关面试题

Q1:TypeScript 7.0 为什么选择 Go 重写编译器,而不是 Rust?

答: 核心原因是 TypeScript 编译器的性能瓶颈在于并行能力,而非单线程速度。Go 的 goroutine + 共享内存模型天然适合大规模并行编译,而 Rust 的所有权系统在共享内存并行场景下编程复杂度更高。此外,Go 的学习曲线更低,TS 团队(JS/TS 专家)能更快上手;Go 的编译速度更快,便于编译器本身的迭代开发。Rust 在绝对运行速度上更优,但对于「需要大量并行 + 快速迭代」的编译器前端场景,Go 的工程效率更高。

Q2:TS 6 的编译器有哪些性能瓶颈?TS 7 是怎么解决的?

答: TS 6 的三个主要瓶颈:① JavaScript 单线程模型导致类型检查只能串行执行;② V8 的 GC 停顿会中断编译流程;③ --watch 模式基于轮询,文件监听效率低。TS 7 的解决方案:① Go 的 goroutine 实现共享内存并行,多个 worker 同时检查不同文件;② Go 的并发 GC 停顿极短,不阻塞编译;③ 将 Parcel watcher 从 C++ 移植到 Go,使用操作系统原生文件监听 API 替代轮询。

Q3:TS 7 的并行类型检查是怎么实现的?为什么能保证结果一致性?

答: TS 7 使用多个 goroutine 作为类型检查 worker,通过共享内存访问同一份全局类型表和符号表。文件之间的类型检查大多互不依赖,可以并行执行。当遇到跨文件类型引用时,worker 通过共享内存直接读取已完成的类型信息。Go 的 sync.Mutex 和 channel 机制保证并发访问的安全性。类型检查逻辑本身没有改变------TS 7 是「重写材料」而非「重写逻辑」,保证了类型检查结果与 TS 6 一致。

Q4:从 5.x 升级到 TS 7.0 有哪些 breaking changes?

答: 主要 breaking changes 集中在配置层面:① strict 默认变为 true;② module 默认变为 esnext(输出 ESM);③ types 默认变为空数组 [],原来隐式可用的 @types/* 全局声明需要显式声明;④ rootDir 需要显式设置,不再自动推断;⑤ target: "es5" 变为硬错误,完全移除;⑥ downlevelIteration 废弃。建议升级前先跑 npx tsc@7 --noEmit 检查报错,再逐项修复配置。

Q5:TS 7 说的「10 倍速」体现在哪些环节?所有操作都快 10 倍吗?

答: 「10 倍速」主要指完整构建(tsc 从头到尾)的速度提升,是解析、类型检查、代码生成所有环节的加速总和。其中解析和代码生成环节的并行收益最大(文件间无依赖,直接多线程),类型检查环节因为需要全局信息共享,并行收益相对较小。日常开发中体感最明显的可能是编辑器内的 LSP 响应速度(代码补全、错误提示等),因为 TS 7 的 LSP 也是 Go 实现,多线程并发响应。不是所有操作都恰好快 10 倍------有些环节快得多,有些环节快得少,综合下来在大型项目上约 8-12 倍。


十、总结

一张图梳理 TS 7.0 的核心变化:

lua 复制代码
TypeScript 7.0 核心变化
│
├─ 编译器重写:TypeScript/JavaScript → Go
│   ├─ 单线程 → 多 goroutine 并行
│   ├─ Worker 消息传递 → 共享内存并行
│   └─ V8 JIT → 原生机器码
│
├─ 性能提升:8-12 倍(大型项目更显著)
│   ├─ 解析/发射:文件级并行(收益最大)
│   ├─ 类型检查:共享内存并行(--checkers 调节)
│   └─ 项目引用:并行构建(--builders 调节,monorepo 福音)
│
├─ --watch 模式重建
│   ├─ 轮询 → 操作系统原生文件监听
│   └─ Parcel watcher 从 C++ 移植到 Go
│
├─ 配置变更
│   ├─ strict: true(默认)
│   ├─ module: esnext(默认)
│   ├─ types: [](需显式声明)
│   ├─ rootDir: ./(需显式设置)
│   └─ target: "es5" → 硬错误
│
└─ 编辑器体验
    └─ LSP 多线程并发响应(补全/跳转/错误提示更快)

TypeScript 7.0 不是语法更新,不是新 API------它是一次基础设施级的换血。

对于开发者来说,升级的成本很低(改几行 tsconfig.json),收益很高(构建速度 10 倍提升 + 编辑器体验质变)。

如果你还在用 TS 5.x 甚至 4.x,现在是一个值得升级的节点。


参考链接:

相关推荐
WaywardOne8 小时前
Flutter组件化方案(AI总结)
前端·flutter·ai编程
玉宇夕落8 小时前
原子化编程”:Tailwind CSS 核心原理
前端
是小李呀8 小时前
解决“Error: The project seems to require yarn but it‘s not installed”报错
前端
weedsfly8 小时前
一个电商价格计算案例,带你学会前端开发中的责任链模式
前端·javascript·面试
__sjfzllv___8 小时前
在职前端Leader学习/转行 AI Agent -DAY10
前端
沉迷学习日益消瘦9 小时前
8 UI 组件库与设计系统:Token 驱动 + 暗色模式 + 4 种主题色
前端
CodeSheep9 小时前
有这4个迹象,你就该离职了!
前端·后端·程序员
用户059540174469 小时前
把大模型记忆回归测试从手工检查换成 Playwright+VCR,误判率从 40% 降到 0
前端·css
倾颜9 小时前
pnpm 负责依赖,Turbo 负责任务:一次小型 Monorepo 治理的边界取舍
前端·next.js