Skip to content
Published on

TypeScript 7 正式 GA — 8~12 倍是真的,但有些人今天还搬不了

分享
Authors

引言 — 7月8日,tsgo 变成了 tsc

2026 年 7 月 8 日,TypeScript 7.0 正式 GA。跑了 1 年多预览版的 Go 原生移植版进入了正式发布。

如果你过去 1 年一直在关注这个项目,tsgo 这个名字你应该很熟悉。此前它作为独立包(@typescript/native-preview)、独立二进制发布 nightly 版本,和现有的 tsc 并排跑是标准做法。到了 GA,这套格局消失了。 官方公告里一次都没出现 tsgo 这个词。取而代之的是这样的说法 — 执行 npm install -D typescript 装进来的就是"新的 tsc 可执行文件"。

也就是说,原生移植版不再是可选项,而是默认值。直接查 npm 注册表可以看到,typescriptlatest 标签是 7.0.2,发布日期是 2026 年 7 月 8 日 — 与公告里的 GA 日期完全一致。而 @typescript/native-preview 的最后一次发布停在 7 月 7 日,也就是 GA 前一天。nightly 版本现在已经转移到了 typescript@next(目前是 7.1.0-dev 系列)。

发布时间线是这样走的 — beta 4 月 21 日,RC 6 月 18 日,GA 7 月 8 日。

本文不是要重新论证"Go 移植版很快"这件事。这一点已经在TypeScript 6 与 tsc-go 移植版一文里、以预览版时期为基准讲过了,说实话,当时设想的发布形态(像 tsc --go 这样的后端标志、一个大版本同时出货两个编译器)和实际的 GA 并不一样。实际情况是,大版本本身直接分裂了 — 6 是 JS 实现,7 是 Go 实现。本文只依据 GA 时确定下来的事实,看看标题数字背后真正的迁移成本。

先把数字搞准 — 到底测的是什么

公告自己的说法是 "10x faster native port",正文里有条件的那句是这样写的 — 原生代码速度、共享内存多线程、以及若干优化,在完整构建(full builds)上通常能带来 8 倍到 12 倍的速度提升

这是 Microsoft 亲自测量并公开的完整构建耗时表。这是厂商自己报告的数字,在同一台机器上用 TypeScript 7 的默认设置测出来的。

代码库          TS 6       TS 7       速度提升
vscode         125.7s     10.6s      11.9x
sentry         139.8s     15.7s       8.9x
bluesky         24.3s      2.8s       8.7x
playwright      12.8s      1.47s      8.7x
tldraw          11.2s      1.46s      7.7x

有两点值得注意。第一,表里的下限是 7.7 倍,比"8~12 倍"这个自我表述略低。第二,速度提升因代码库而异 — vscode 的 11.9 倍和 tldraw 的 7.7 倍相差 1.5 倍。看起来代码库越大,并行化收益越大。

内存数据也一并公开了,而这恰恰是应该降低预期的地方。

代码库          TS 6      TS 7      变化
vscode         5.2GB     4.2GB     -18%
sentry         4.9GB     4.6GB      -6%
bluesky        1.8GB     1.3GB     -26%
playwright     1.0GB     0.9GB     -11%
tldraw         0.6GB     0.5GB     -15%

这和预览期流传的"内存减半"说法不一样。实际的 GA 数字是下降 6% 到 26%,sentry 几乎原地踏步。相比速度跳了一个数量级,内存只是温和改善。公告自己的说法也只到"通常在完整构建中需要更少的总内存"为止,没有再多主张。

编辑器这边也有数字。在 VS Code 代码库中打开一个有错误的文件,看到第一个错误出现,同一台机器上以前大约要 17.5 秒,TypeScript 7 上不到 1.3 秒 — 也就是快了 13 倍以上。

作为企业案例报告的数字需要更谨慎地读一层。这些是 Microsoft 表示是从各团队那里听来的数值。

  • Slack — CI 类型检查时间从约 7.5 分钟降到 1.25 分钟。据称合并队列时间因此消失了 40%。此前由于编辑器语言服务器的加载时间,本地开发几乎"没法用",工程师们把整体类型检查都甩给了 CI,现在同一个代码库据称几秒就能加载完。
  • Canva — 编辑器里看到第一个错误的时间从约 58 秒降到约 4.8 秒。
  • Vanta — 在其最大的一个项目上最高达到 9 倍。
  • Microsoft News Services 团队 — 每月节省了原本花在等待 CI 构建上的 400 小时。

还有稳定性数字。Microsoft 表示,根据自家数据洞察,7.0 的新语言服务器相比 6.0,把失败的语言服务器命令减少了 80% 以上,服务器崩溃减少了 60% 以上。这是基于遥测的自我测量,不是外部能够复现的那类数字。

"10倍"是有旋钮的 — --checkers

这是 GA 才揭晓、而且大多数摘要文章都漏掉的部分。那些基准数字,是某个特定并行化设置下的结果。

TypeScript 7.0 把解析、类型检查、生成(emit)并行执行。解析和生成按文件几乎相互独立,所以并行化效果很好。问题出在类型检查上 — 文件之间的依赖很复杂,大多数文件都要依赖同一个全局作用域和依赖项的类型信息。而且类型检查有时还依赖程序内信息的相对顺序,要让结果保持一致,就必须始终按同样的顺序检查。

于是 TypeScript 7 会创建固定数量的类型检查器 worker。每个 worker 都有自己的一套世界观,会重复做一部分公共工作,但只要输入相同,就总是以同样的方式切分文件,产出同样的结果。

默认的 worker 数量是 4。前面的表全都是用默认的 --checkers 4 测的,公告还在同一台机器上用 --checkers 8 重新测了一遍,把表格并排放了出来。

代码库          TS 6       TS 7 (--checkers 8)    速度提升
vscode         125.7s     7.51s                  16.7x
sentry         139.8s    12.08s                  11.6x
bluesky         24.3s     2.01s                  12.1x
playwright      12.8s     1.16s                  11x
tldraw          11.2s     1.06s                  10.6x

把检查器数量提到 8,vscode 能到 16.7 倍。但这不是免费的 — 用公告自己的话说,增加检查器数量通常要付出内存占用增加的代价。而且结果会因项目和机器而异。

方向也可以反过来。在 CPU 核心和内存较少的环境里 — 公告直接举的例子就是 CI runner — 反而可能应该调低这个值,避免不必要的开销。可以一路调到 --checkers 1,这样类型检查实际上就变成单线程,重复工作也就消失了。

这里出现了实务上最重要的一句话。

极少数情况下,改变 --checkers 的数量可能会暴露出依赖顺序的结果差异。

也就是说,团队自己承认了检查器数量可能影响诊断结果。公告的建议是在整个构建环境中固定一个检查器数量,让所有人拿到相同的结果,但也补充说这终究是团队自己的裁量。如果本地是 8、CI 是 2 这样各自为政,原理上就为"我机器上能过,CI 上报错"这种情况留了口子。换作是我,无论是 tsconfig.json 还是 CI 脚本,我都会把这个值钉死在一个地方。

还有两个相关的标志。--builders 用来调节 --build 模式下同时运行的项目引用构建器数量(面向 monorepo)。要注意的是它--checkers 是相乘关系 — 就照公告自己的例子,--checkers 4 --builders 4 允许最多 16 个类型检查器同时运行,这可能会过头。--singleThreaded 会彻底关掉并行化(用于调试、比较 6 和 7 的性能、由外部工具协调并行构建的场景,或资源极其紧张的环境)。--checkers--builders 目前都是作为实验性(experimental)标志引入的。

--checkers 不同,文档明确说明改变 --builders 的数量不应改变结果。相反,项目引用构建从根本上会被项目依赖图卡住瓶颈。

真正的成本(1) — TypeScript 6 的全部废弃预告变成了硬错误

接下来是该付钱的部分了。

兼容性本身是好的。公告里带条件的那句是这样写的 — 在打开 stableTypeOrdering 标志、且未设置 ignoreDeprecations 标志的情况下,能在 TypeScript 6.0 下干净编译的代码,几乎不管是什么,在 7.0 下都应该能同样编译通过。

问题就出在这个前提上。7.0 原样采纳了 6.0 的新默认值,并且对 6.0 里已经预告要废弃的标志和语法给出硬错误。正如公告自己承认的,"6.0 相对还比较新,很多项目还需要适应它的新行为"。所以官方的建议是不要直接跳到 7,而是先引入 6.0,让迁移更顺畅

先看默认值的变化。

strict                        -> 默认 true
module                        -> 默认 esnext
target                        -> esnext 之前最近的稳定 ECMAScript 版本
noUncheckedSideEffectImports  -> 默认 true
libReplacement                -> 默认 false
stableTypeOrdering            -> 默认 true,且无法关闭
rootDir                       -> 默认 ./ (内部源码目录需要显式指定)
types                         -> 默认 [] (以前的行为是 ["*"])

公告里点名的"最令人意外"的变更是 rootDirtypes 这两个。两者都很容易缓解。tsconfig.json 位于 src 这类目录之外的项目,只要想保留目录结构,显式指定 rootDir 就行。

{
  "compilerOptions": {
    "rootDir": "./src"
  },
  "include": ["./src"]
}

types 这边,依赖全局声明的项目需要自己列出所需的内容。

{
  "compilerOptions": {
    "types": ["node", "jest"]
  }
}

看起来无害,但这恰恰是个安静的地雷。types 默认值变成 [] 之后,自动探测就关掉了 — 如果你此前只是装上 @types/node@types/jest,指望它们被自动捡起来,在 7 上会突然冒出一堆"找不到全局符号"的报错。不是代码错了,是配置变了,修法也只是一行,但在一个大型 monorepo 里逐包踩一遍,是件挺磨人的活。

strict 默认变成 true 也是同样的道理。对一个一直以 non-strict 方式生存下来的旧代码库来说,这不是"换个编译器",而是"重写类型"。为了换来一个快 8 倍的编译器,可能得先预付几个月的 strictNullChecks 工作量。

还有彻底消失的东西 — 不是空操作(no-op),是硬错误。

  • target: es5 支持终止
  • downlevelIteration 支持终止
  • moduleResolution: nodenode10 支持终止(推荐 nodenextbundler)
  • module: amdumdsystemjsnone 支持终止(推荐 esnextpreserve)
  • baseUrl 支持终止(把 paths 更新为相对于项目根目录的相对路径)
  • moduleResolution: classic 支持终止
  • esModuleInteropallowSyntheticDefaultImports 不能设为 false
  • alwaysStrict 始终被视为 true,不能设为 false
  • 命名空间声明中不能使用 module 关键字
  • import 中不能使用 asserts 关键字 — 要按 ECMAScript import attribute 语法改用 with
  • skipDefaultLibCheck 下,/// <reference no-default-lib /> 指令不再被遵循
  • 当前目录存在 tsconfig.json 时,命令行构建无法再接受文件路径(除非显式传入 --ignoreConfig 标志)

这份清单里,最让实务疼痛的是 baseUrltarget: es5baseUrl 广泛埋在旧项目的绝对路径 import 里,而 target: es5 对还必须支持旧版浏览器的团队来说,可能是一个不可谈判的要求。如果需要 es5,7 就不属于你。这不是靠改改配置就能跨过去的那种墙。

最后一条也出乎意料地容易踩到。不少团队把 npx tsc src/foo.ts 这类命令写进了脚本里,而只要项目根目录存在 tsconfig.json,现在就直接报错。

真正的成本(2) — 没有 API,所以 Vue、MDX、Astro、Svelte 今天还搬不了

这是 GA 公告里最重要、却被引用得最少的部分。

TypeScript 7.0 出了,但不出货 API。

没有编程 API。公告写道,预计会在 7.1 中出货一个新的(而且不同的)API,在那之前,优先事项是让 6 和 7 能够并排运行。

影响是直接的。照搬公告"TypeScript and Embedded Languages"一节的说法 — 使用 Vue、MDX、Astro、Svelte 等的工作流,很可能暂时还用不上 TypeScript 7。 像 Angular 那种模板内部的专门类型检查也是一样。原因是 7 还没有暴露出稳定的编程 API,所以那些把 TypeScript 嵌入自己编译器和语言服务里的工具 — 比如 Volar — 目前只能依赖 TypeScript 6.0。

这不是"可能会慢一点",而是用不了。前端生态系统相当大的一块都卡在这里。Vue 团队、Astro 团队、Svelte 团队,以及所有基于 MDX 的文档/博客流水线,全都架在 Volar 之上。

建议因此分成了几类。

  • 不需要语言服务器插件的场景下使用 7 — 这是基本建议。
  • Angular — 可以组合使用:CLI 里用 tsc 做快速的项目级错误检测,编辑器支持则用 6.0。也就是说能拿到半份收益。
  • Vue、MDX、Astro、Svelte 等 — 目前还得继续用 6.0。在 VS Code 里可以用"Disable TypeScript 7 Language Server"命令切回 6.0。

Microsoft 把这看作一个"特定时间点的问题(point-in-time issue)",承诺会提供解决方案,并表示会积极和相关项目的维护者合作。我认为这是一个可信的承诺 — 但承诺修不好今天的构建。 从 7.1 的新 API 出炉,到 Volar 移植适配,再到 Vue/Astro/Svelte 各自的工具链跟进采用,中间要经过好几个发布周期。公告自己透露的发布节奏是每 3 到 4 个月一次,下游采用的时间还要再叠加上去。

让 6 和 7 共存 — tsc6 与 npm 别名

所以现实的答案通常是"两个都留着"。为此 Microsoft 新出了一个兼容包。

@typescript/typescript6 提供了一个叫 tsc6 的可执行文件。这样一来,它就能和自带 tsc 二进制的 7.0 并排安装,而不会撞名。这个包还重新导出了 TypeScript 6.0 的 API,所以你可以让 tsc 用 7,同时让其他工具链继续依赖 6.0。查 npm 注册表可以看到,这个包的 latest6.0.2bin 字段正好只有一项:tsc6

像 typescript-eslint 这样的工具,是通过 peer dependency 期望直接从 typescript 里 import 的,所以推荐的做法是用 npm 别名。

npm install -D typescript@npm:@typescript/typescript6

不过只做这一步,留下的就只有 tsc6 这个可执行文件。要想同时拿到 7.0 的 tsc,还得再加一个别名。

{
  "devDependencies": {
    "@typescript/native": "npm:typescript@^7.0.2",
    "typescript": "npm:@typescript/typescript6@^6.0.2"
  }
}

这样一来,npx tsc 跑的是 7.0,而那些 import typescript 的工具看到的是 6.0。

说实话,这不是一个优雅的解法。package.json 里绞在一起两个别名,typescript 这个名字实际指向的却是 6,第一次看到的人肯定会懵。但如果你在用 typescript-eslint,又想让 tsc 跑得快 — 也就是现实中相当一部分项目的处境 — 目前这就是官方路径。

悄悄改变的地方 — JSDoc 与 Unicode

被性能标题盖过去了,但确实可能破坏代码的变更还有两处。

JavaScript 支持被重做了。 原本 TypeScript 是通过识别 JSDoc 注释和特定代码模式来支持 JS 文件的,这经常是一堆基于 Closure 或 JSDoc 文档生成工具理解方式的特例。7.0 把这部分重写成和 .ts 文件的分析方式保持一致。结果是有些东西消失了。

  • 不能在类型该出现的位置写值 — 要写成 typeof someValue
  • @enum 不再享有特殊待遇
  • 不能把单独的 ? 当类型用 — 用 any
  • @class 不会再把函数变成构造函数 — 要用类声明
  • 不支持后置 !
  • 类型名必须在 @typedef 标签内部定义(放在标识符旁边的写法不再允许)
  • 不支持 Closure 风格的函数语法 — 用 TypeScript 的简写形式

this 起别名,或者整体重新赋值函数原型的这类模式,也失去了特殊处理。如果你有一个用 JSDoc 标注类型的大型 JS 代码库,这里才是真正的迁移工作量所在。TypeScript 团队表示他们另外维护着一份记录 6.0 和 7.0 差异的 CHANGES.md 文件,并把 JS 支持描述为仍"处于变动中(in flux)"。

模板字面量类型现在会保留 Unicode 码点。

type HeadTail<S> = S extends `${infer Head}${infer Tail}` ? [Head, Tail] : never;
type Result = HeadTail<"😀abc">;
//   在 7.0 中: ["😀", "abc"]
//   此前:      ["\ud83d", "\ude00abc"]

以前是按照 JavaScript 的 UTF-16 索引行为,把代理对(surrogate pair)从中间切开。技术上这和 JS 的索引方式是一致的,但通常并非本意,还可能产生带着没有意义的、落单代理项的字符串字面量类型。新的行为和用 for...of 遍历或展开(spread)时的直觉是一致的。

公告很诚实地把这标记为一次针对刻意建模 UTF-16 code unit 的类型级字符串操作的破坏性变更 — 举的例子是一些字符串 Length 工具类型。如果你在使用或构建做深度类型级字符串编程的库,需要检查一下。

还有一点。--watch 模式被彻底重做了。新的基础是把 Parcel 打包器的文件监视器移植到了 Go(原本是 C++,需要一整套 C++ 工具链,现在用一个最小的汇编垫片把它搬到了 Go)。原因是 Go 标准库没有文件监视 API,而基于轮询的方案在 node_modules 依赖很多的大型项目上代价太高。公告报告说,--watch 在各平台上的资源占用都有大幅改善。

那么,你今天该不该迁移

归纳起来是这样。

今天迁移值回票价的情况

  • 已经在用 TypeScript 6.0,打开 stableTypeOrdering、没有设置 ignoreDeprecations 也能干净构建 — 这正是官方兼容性声明所保证的确切条件。这种情况下,迁移基本上很顺畅。
  • .ts/.tsx 代码库,且不使用 Volar 系的嵌入式语言工具链。
  • CI 类型检查是真正的瓶颈 — 8 倍在这里能直接变现。
  • 编辑器的响应速度是团队实实在在的痛点。

目前还搬不了的情况

  • 在用 Vue、MDX、Astro、Svelte — 这不是选择题。得等 7.1 的 API,以及随后的 Volar 移植。
  • Angular — 只能拿到一半。CLI 类型检查用 7,编辑器用 6。
  • 需要 target: es5 — 是一堵墙。
  • 还在 TypeScript 5.x,且是 non-strict — 不要直接跳到 7。先经过 6 是官方建议,而且是有理由的:与其一次性踩完整份废弃清单,不如在 6 里以警告的形式逐步清理。
  • typescript-eslint 这类依赖 API 的工具链是核心工作流 — 靠前面那套别名组合是可行的,但这份复杂度值不值得承担,得由团队自己判断。

再说一条关于顺序的建议。如果要迁移,从一开始就明确固定 --checkers 的值。 本地和 CI 跑在不同的默认并行度下,一旦碰上依赖顺序的诊断差异,那是最不想调试的一类 bug。既然团队自己都写明"极少数情况下可能暴露出来",把值钉死的成本是一行代码,不钉死的成本是大半天。

结语

TypeScript 7.0 的性能故事是真的。虽然是厂商自报,但是拿可复现的开源代码库测出来的,条件(默认 --checkers 4、完整构建)也公开了,甚至还把 --checkers 8 的表格一并放出来,自己就说明了数字会随配置变动。做到这个程度,作为厂商基准测试已经算诚实的了。如果 Slack 那种从 7.5 分钟到 1.25 分钟的数字能复现,在大代码库上,这就是生活质量问题。

但标题是"快了 10 倍",而真实的故事是"快了 10 倍,代价是要把 6 的整份废弃清单清算干净,而且前端生态里很大一块,在 API 出来之前都还没被邀请入场"。

所以我是这么看的 — 7.0 是一次已经开始、但还没完成的迁移。 在 7.1 带着 API 出现、Volar 站上去之前,这个生态系统还得有一阵子分裂成 6 和 7 两边生活。@typescript/typescript6tsc6 这类兼容包的存在本身,就是对这一点的承认。Microsoft 自己也清楚这一点,所以公告的结尾写道,希望"用 7.1 补上任何缺口,把社区带向前"。

如果你是纯 TS 代码库,今天是个好日子。如果你站在 Vue 或 Astro 之上,这次发布暂时还不属于你 — 而这不是因为你做错了什么。

参考资料