
这是 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.ts 和 b.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.ts,b.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/* | \[\](空列表) | 必须显式声明 |
其中 rootDir 和 types 是最容易踩坑的变更。
types 从「自动加载所有 @types/*」变成了「空列表」。这意味着原来隐式可用的全局声明(如 node、jest、bun)现在需要显式声明:
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,现在是一个值得升级的节点。
参考链接: