TypeScript 7.0(译)
前言
原文地址:Announcing TypeScript 7.0
正文
一个更快的 TypeScript 意味着什么
一个更快的 TypeScript 纸面上听起来很棒,但它在实践中意味着什么?或许思考 TypeScript 在每个开发阶段出现的地方会有帮助。
一个开发者典型的一天包括,打开编辑器,打开一个 TypeScript 文件,执行类似“在项目中寻找所有引用”的操作。当你开始编辑时可能会期待弹出代码自动完成,以及在编辑过程中随时弹出红色波浪线,当你(在当下或许是一个 AI 智能体)准备好构建项目的时候,执行 tsc 命令,检查输出文件是否存在错误,接着使用某种方式执行生成的代码。
一个更快的 TypeScript 意味着上面提到每个步骤都能得到简化。等待编辑器完全加载项目这个操作体感上会瞬间完成。寻找所有引用,自动完成以及代码诊断只需消耗以前所用时间的一小部分。当你执行 tsc ,或许加上了 --watch 模式,反馈周期将被缩短,迭代速度要远快于以前。
这里有一些开始使用 TypeScript 7 的真实项目。你也可以自己尝试比较一些开源项目,下面是一些相当大的开源代码库在使用 TypeScript 6 和 TypeScript 7 所需的构建时间。
| Codebase | TypeScript 6 | TypeScript 7 | Speedup |
|---|---|---|---|
| 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 |

TypeScript 7 通常在整个构建过程中也有更少的内存占用。
| Codebase | TypeScript 6 | TypeScript 7 | Memory Delta |
|---|---|---|---|
| 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% |

当然,体验不止完整的构建过程。在相同的电脑上,打开 VSCode 代码库中一个带有一个错误的文件,从打开编辑器到看见第一个错误,先前花费大概 17.5 秒,而使用 TypeScript 7 ,下降到 1.3 秒,超过 13 倍的速度提升。
广泛测试,准备好用于生产环境
在十余年的开发中,TypeScript 项目包含数万个测试用例, main 分支上的每个提交都会执行这些测试用例。这确保了每个 release 版本都是稳定且可靠的。
不过 TypeScript 7 不是一个普通的 release 版本。除了这些测试套件之外,我们还利用了许多不同的资源,来确保 TypeScript 7 能稳定地用于生产环境上。
过去一年我们与许多内部的、外部的大型团队配合工作,测试 TypeScript 7 在真实代码库上的表现,结果非常可观,全部的公司都表示 TypeScript 7 稳定,快速,易于使用。比如, VSCode 团队近期特别发表了使用 TypeScript 7 预览版的体验,TypeScript 7 的使用加快了他们的开发周期。我们也和微软内的 Loop 、 Office 、 PowerBI 、 Teams 以及 Xbox 等团队配合工作,确保 TypeScript 已准备好应用于最大的代码库。还有,像 Bloomberg 、 Canva 、 Figma 、 Google 、 Lattice 、 Linear 、 Miro 、 Notion 、 Sentry 、 Slack 、 Vanta 、 Vercel 、 VoidZero 等公司也配合我们测试 TypeScript 7 在他们代码库上的表现,并给与我们反馈让 TypeScript 变得更好。
此外,我们还重构了许多广泛的测试基础设施,用于适配 TypeScript 7 , TypeScript 6 及之前的版本会自动按需地为那些 Github 上的 TypeScript 和 JavaScript 项目进行测试,以检测编译器和语言服务的回归问题。相同的测试已回归,在真实的代码库中使用 TypeScript 7 进行测试查找问题,这样可以发现核心测试套件的不足,提供更好的用户体验。
显式的反馈,自动的崩溃报告以及积极的测试,这些过程的结合使得 TypeScript 在质量方面有了可衡量的变化。事实上,我们的数据已经向我们展示了 TypeScript 7.0 新的语言服务端对比 TypeScript 6 ,实际上减少了超过 80% 的执行失败的语言服务命令,减少了超过 60% 的语言服务端崩溃。
我们也听到了大量来自不同团队中令人难以置信的反馈:
- Slack 工程师告诉我们 TypeScript 7 减少了 40% 的合并队列时间,并且 CI 上的类型检查时间大约 7.5 分钟降到了 1.25 分钟。本地开发由于语言服务的加载时间以及工程师通常会让 CI 做一个完整的类型检查,几乎“不可用”,而 TypeScript 7 花费几秒就可以加载同一个代码库,使得本地检查再次成为可能。
- Vanta 的构建得到了显著地提升,在他们最大的项目中,速度提升高达 9 倍。
- 类似的,微软的 News Services 团队告诉我们使用 TypeScript 7 每个月在等待 CI 构建上节省了 400 小时。
- 去年, PowerBI 的工程师将 TypeScript 7 描述为“拯救了他们在代码库上的工作”。甚至在 TypeScript 7 在 VSCode 上支持重命名功能之前,他们就已使用 TypeScript 7 作为默认选项。
- 为 Loop 单仓库工作的开发者也非常开心。先前的编辑器体验在他们的规模下无法使用,而 TypeScript 7 的使用体验是惊人的。
- Canva 的开发者告诉我们 TypeScript 7 的语言服务带来了惊人的速度提升,在他们的编辑中从大约 58 秒观察到第一个错误降低到了 4.8 秒。
同时运行 TypeScript 6.0
虽然 TypeScript 已发布,但它不自带编程式 API 。我们希望 TypeScript 7.1 附带一个新的 API (与先前的不同),但在这之前我们需要优先确保 TypeScript 7.0 可以和 TypeScript 6.0 并行,以便那些需要以编程的形式访问编译器的工作能正常工作(比如 typescript-eslint )。
作为 6.0/7.0 过渡进程的一部分,我们发布了一个新的兼容性的包 @typescript/typescript6 。这个包提供了名为 tsc6 的可执行命令,所以如果需要,你可以同时安装 TypeScript 7.0 (附带了 tsc 二进制命令)而不会出现命名冲突。新的包也重新导出了 TypeScript 6.0 的 API ,所以你可以使用 TypeScript 7.0 的 tsc ,而其他工具继续依赖 6.0 。
由于某些工具,比如 typescript-eslint ,需要从对等依赖项直接从 typscript 包导入,我们推荐使用 npm 的别名来实现它。你可以执行下面的命令:
1 | npm install -D typescript@npm:@typescript/typescript6 |
或者将 package.json 内的 typescript 路径改为如下:
1 | { |
注意这么做后只有 tsc6 命令了。为了拿到 7.0 的 tsc ,你可以为 TypeScript 7 添加另一个别名,这样 npx tsc 就会工作在 7.0 下:
1 | { |
每日构建版本 @typescritp/native-preview
直到现在,许多开发者通过 @typescript/native-preview 安装 TypeScript 7 。这个包附带了新代码库的每日构建版本,为社区提供了每周超过 850 万的下载。
不过,在不久的将来,每日构建版本将会恢复到标准 typescript 包的 next 标签下。你可以通过如下命令安装它:
1 | npm install -D typescript@next |
自定义规模:并行和控制
TypeScript 7 现在很多步骤都是并行执行的,包括解析,类型检查以及代码生成。这些步骤中的一部分,像解析和代码生成几乎可以跨文件独立完成。像这样,并行化能够自动很好地扩展到更大的代码库,并带来相对更小的开销,但并不是每个 TypeScript 的构建步骤都能简单地并行。
TypeScript 7 引入了实验性质的 --checkers 和 --builders 标志,来为那些不那么琐碎的步骤,比如类型检查和项目引用构建,微调其并行行为。同时也引入了 --singleThreaded 标志来完全禁用并行特性,这对那些资源有限的环境的调试或执行操作很有帮助。
类型检查器并行
其他一些步骤,比如类型检查,它在跨文件间有着更复杂的依赖关系。许多文件最终依赖于来自其自身依赖和全局作用域的相同类型信息,所以完全独立地运行类型检查器是浪费的,包括计算和内存占用。另一方面,类型检查器偶尔会依赖程序中的相对顺序信息,所以从零开始的类型检查必须总是按照相同的顺序检查相同的文件,以确保获得相同的结果。
为了在避免这些陷阱的情况下启用并行, TypeScript 7.0 创建了一部分固定数量的,有独立状态的类型检查器线程,这些线程可能最终会重复某些共同的工作,但给与相同的文件输入,它们总是能将这些文件划分为相同的部分,并产生相同的结果。
默认的类型检查器线程数量为 4 ,它可以通过新的 --checkers 标志来配置。你可能会发现增加这个数量在 CPU 核心更多的机器上可以进一步加速构建更大型的代码库,但通常情况下也会带来内存使用量的增加。比如在上面的表格展示了 TypeScript 7 在默认 --checkers 4 执行的结果,而下图则展示了在相同机器下,在 --checkers 8 的执行结果。
| Codebase | TypeScript 6 | TypeScript 7 (–checkers 8) | Speedup |
|---|---|---|---|
| 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 |
正如你所看到的那样,这些代码库使用更多的核心从而得到了一个更快的速度,但不同的项目和不同的底层机器使得结果有所不同。
另一方面,在更少核心和更少内存的机器上(比如 CI 执行器),你可能想要减少这个数字来避免不必要或额外的开销。你可以将数字设置到很小,比如 --checkers 1 ,有效地让类型检查单线程话,并且消除重复的工作。
极少数情况下, --checkers 的数量变化可能会导致结果出现顺序依赖性问题。跨构建环境为 --checkers 指定一个固定的数量可以确保每个人得到相同的结果,但这由你们团队决定。
项目引用构建器并行化
TypeScript 7.0 不仅可以在一个项目内并行构建,还可以立刻同时构建多个项目。这个行为可以通过配置新的 --builders 标志,这个标志控制了在执行 --build 时项目应用构建器并行的数量。这对那些有多个项目的单仓库有特别的帮助。
就像 --checkers ,增加构建器的数量可以加快构建速度,但会带来内存使用量的增加。它和 --checkers 还会产生乘法效应,所以为代码库和机器找到一个正确的平衡是很重要的,比如,使用 --checkers 4 --builders 4 允许至多 16 个类型检查器同时执行,这个数量过多了。
与 --checkers 不同,--builders 的数量变化不会产生不同的结果。然而,构建项目引用从根本上受限于项目的关系依赖图(除了在代码库上利用 --isolatedDeclarations 和分开生成与发声明来进行代码检查)。
单线程模式
在某些情况下,强制整个编译器运行在单线程下是很有帮助的。这有助于调试、对比 TypeScript 6 和 TypeScript 7 的性能,在外部协调并行构建,也有助于在资源非常有限的环境中执行。启用单线程模式,可以使用新的 --signleThreaded 标志,这不仅会将类型检查器的线程覆盖为 1 ,也会确保解析和代码生成在一个线程中完成。
提升 --watch 模式性能
TypeScript 7 附带了一个完全重建的 --watch 模式。现在 --watch 由一个基于 Parcel 捆绑器的文件监视器的新基础提供支持,它提供了高效和稳定的跨平台文件监视能力。
当我们的团队开始着手移植我们的文件监视逻辑时,我们遇到了一些 Go 在跨平台文件监视的挑战。标准库并没有提供内置的监视 API ,我们浏览了现有的三方库,但它们在稳定性,性能,跨平台支持,构建工具集成等方面存在问题。我们使用了定时轮询来检测文件的变更,这种方法在许多操作系统中普遍适用。但是这样的计算是很昂贵的,特别是在面对有着许多依赖的 node_modules 的大规模的项目。即使使用动态调度策略,我们也发现纯轮询的解决方法在普遍使用情况下依然费力。
许多年来, VSCode 依赖 @parcel/watcher ,近些年来在 VSCode 中的 TypeScript 已经直接依赖了其文件监视能力。虽然它看起来很棒,但是对我们来说有一个问题,就是这个库是由 C++ 写的,也就是说它需要一个完整的 C++ 工具链用于构建。鉴于我们在 VSCode 中使用 Parcel 的监视器时获得的良好体验,我们尝试将其移植到 Go 上,配合上一些垫片来避免引入新的工具链依赖。
这个尝试是成功的,最初只是将 C++ 代码直接翻译到 Go 上,之后进一步地改为地道的 Go 代码并且其仍能通过移植的测试套件。监视器是一个独立的包,它允许我们清晰地区分“要监视什么文件”以及“为什么要监视”。我们现在可以观察到在各个平台上的 --watch 模式下的效率都有极大的提升,我们也收到了来自 TypeScript 7 早期用户的积极反馈。
感谢 Devon Govett ,他在 Parcel 上的工作给 VSCode 和 TypeScript 项目带来了巨大的好处。我们希望这个移植版本能够随着时间推移为原本的 Parcel 监视器代码库带来灵感。
自 5.x 以来的更新,以及 6.0 新的行为
TypeScript 7.0 兼容 TypeScript 6.0 的类型检查和命令行行为。几乎所有能够被 TypeScript 6.0 编译的的 TypeScript 代码(启用 stableTypeOrdering 标志,并且没有任何 ignoreDeprecations 标志)都能直接被 TypeScript 7.0 编译。
正如我们所说, TypeScript 7.0 采用了 6.0 新的默认设置,当遇到任何在 TypeScript 6.0 弃用的标志和结构时发出硬性的错误。值得注意的是 6.0 仍是一个较新的版本,许多项目还需要适应这些新的行为。我们鼓励开发者使用 TypeScript 6.0 ,这样能更加简单地迁移到 TypeScript 7.0 ,你可以阅读 TypeScript 6.0 相关博客来获取更多关于这些弃用项的信息。
简而言之,需要注意的默认变更配置包括:
strict默认为true。module默认为esnext。target立即默认为当前稳定的 ECMAScript 版本,也就是在esnext的上一个版本。noUncheckedSideEffectImports默认为true。libReplacement默认为false。stableTypeOrdering默认为true,并且无法关闭。rootDir默认为./,内部的源代码目录必须显式地指定。types默认为[],旧的行为可以通过覆盖["*"]来设置。
我们认为 rootDir 和 types 的变更是最令人“惊喜”的,但它们可以简单地迁移。如果 tsconfig.json 位于项目文件夹的外面,比如 src ,则需要包含在 rootDir 中以保持相同的文件夹结构。
1 | { |
对于 types 变更,依赖指定全局定义的项目需要显示地列出它们自身:
1 | { |
以下是那些已经变为空行为,并且会报错的废弃项:
target: es5不再支持。downlevelIteration不再支持。moduleResolution: node/node10不再支持,现在推荐使用nodenext和bundler。module: amd, umd, systemjs, none不再支持,现在推荐使用esnext或者preserve结合打包器或者基于浏览器模块的解析器。baseUrl不再支持,现在paths根据项目的根进行相对定位。moduleResolution: classic不再支持,现在推荐使用bundler或者nodenext。esModuleInterop和allowSyntheticDefaultImports不可设为false。alwaysStrict默认为true,并且无法更改为false。module关键字不可在命名空间声明中使用。asserts关键字不可在导入语句中使用,必须配合with使用(为了对其 ECMAScript 的导入属性语法的进展)。/// <reference no-default-lib />指令在skipDefaultLibCheck下不再识别。- 命令行构建不可在一个包含
tsconfig.json的文件夹下包含文件输入,除非显式指定--ignoreConfig标志。
模板字面类型现在支持 Unicode 代码点
TypeScript 7.0 现在在推断模板字面类型时会更自然地处理 Unicode 代码点,比如:
1 | type HeadTail<S> = S extends `${infer Head}${infer Tail}` ? [Head, Tail] : never |
先前, TypeScript 遵循 JavaScript 的 UTF-16 索引行为,将 "😀" 分为一个代理对的两半(即 \ud83d and \ude00 )。技术上来讲这与 JavaScript 的索引行为是一致的(比如,对 Head 类型的推断等效于 "😀abc"[0]),但通常情况下并不是开发者的意图,而且产生了包含未配对的,语义上无意义的代理对。
对于特意模拟 UTF-16 代码单元的类型等级字符串的操作来说,这是一个破坏性的变更,例如某些计算字符串长度的工具,实际上,我们预计新的行为会更加有用并且减少对用户的影响:模板字面推断现在和使用 for...of 迭代字符串或者通过 [...str] 展开符号展开字符串的行为保持一致,也就是 "😀" 会被视为一个单元。
与 JavaScript 不同
当我们迁移现有的代码库时,我们借此机会也重新审视了我们是如何进行 JavaScript 的支持工作。
TypeScript 原本支持通过 JSDoc 注释和识别某些代码模式进行分析和类型推断来支持 JavaScript 文件。很多时候,这基于流行的代码模式,但偶尔会基于 Closure 和 JSDoc 文档生成工具工具能够理解的任何人编写的内容。这个方法有助于开发者编写不规范的 JSDoc ,这需要在多处妥协并且处理一些特殊的情况才能正常工作,并且与 TypeScript 对 .ts 文件的分析存在一些差异。
在 TypeScript 7.0 ,我们重新实现了对 JavaScript 的支持,使其和对 TypeScript 文件的分析方式保持一致。其中包括:
- 需要类型的地方不能使用值,除了
typeof someValue @enum不再被特殊识别,而是通过创建一个@typedef (typeof YourEnumDeclaration)[keyof typeof YourEnumDeclaration]- 单独的
?不再作为一个类型使用,替换为any @class不会将函数视为一个构造器,应该使用class来定义- 后置
!不再支持,请使用T。 - 类型命名必须在
@typedef内进行定义(比如:/** @typedef {T} TypeAliasName */),而不能挨着标识符(比如:/** @typedef {T} */ TypeAliasName;) - 闭包式函数语法(比如:
function(string): void)不再支持,请使用 TypeScript 的简化方式(比如:(s: string) => void)。
此外,一些 JavaScript 模式,比如 this 别名,重写整个函数的 prototype 属性不再被特别对待。
某些 JS 支持仍在调整中,我们会更新 CHANGE.md 文件来描述 TypeScript 6.0 和 7.0 间更多不同的细节。
编辑体验
正如我们如上提到, TypeScript 7.0 的性能提升不局限于命令行体验,也包括编辑体验。对于 VSCode 用户,我们创建了一个专门用于的 TypeScript 7 的扩展。当你安装这个扩展后,便会自动地切换到 TypeScript 7 的默认体验。你可以通过命令面板的 “Disabled TypeScript Language Server” 和 “Enable TypeScript Language Server” 命令随时禁用或启用它。在几个星期后 VSCode 自身便会附带对 TypeScript 7 的支持。
对于 VS 的用户,最新版本的 IDE 已会在工作区自动启用 TypeScript 7 ,无需进行任何的变更。
当然, TypeScript 7 可以在你选择的任何编辑工具上良好地运行。新的基础设施建立在语言服务协议(LSP),它能够利用多线程尽可能快地同时服务多个请求。
当 TypeScript 7 第一次亮相的时候,我们添加了缺失的功能,比如自动导入,悬停面板,行内提示,代码引用信息,转到源码定义,JSX的联动编辑和标签的自动完成等等。7.0 beta 以来缺失的特性,比如语法高亮,导入排序,删除无用导入等等也已实现。
此外,在过去的几个月,我们持续的提升性能和稳定性。我们重构了许多测试和诊断的基础设置,来确保质量处于高标准状态,我们也在 Github 上对那些热门的 TypeScript 和 JavaScript 代码库的语言服务进行模糊测试。就像我们上面提到的那样, TypeScript 7 的新的语言服务比起 TypeScript 6 更加稳定。
TypeScript 集成
值得一提的是像 Vue 、 MDX 、 Astro 、 Svelte 等工作流还无法利用 TypeScript 7 。类似地,像 Angular 这样的专门用于对模板进行类型检查的工具也无法使用 TypeScript 7 。这主要是因为 TypeScript 7 还未暴露出一个稳定的编程式的 API ,所以这些集成了 TypeScript 到他们自身编译器和语言服务的工具(类似 Volar)目前只能依赖 TypeScript 6.0 。我们预计这只是一个暂时性的问题,我们也致力于提供一个解决方法。我们也会与这些项目的维护者保持联系以确保 TypeScript 7 可以支持这些工作流。
在此之前,我们推荐团队在无需使用语言服务的场景下使用 TypeScript 7 。使用 Angular 的项目可以使用 TypeScript 7 的 tsc 命令来快速检测项目的绝大多数错误,而在编辑支持上则使用 TypeScript 6.0 。其他使用 Vue 、 MDX 、 Astro 、 Svelte 等项目目前需要继续使用 TypeScript 6.0 。在 VScode 中,用户可以简单地执行 “Disable TypeScript 7 Language Server” 命令来恢复到 TypeScript 6.0 的环境。
路线展望
TypeScript 7.0 在项目上是一个及其重大的里程碑。我们的团队专注这项迁移已超过了一年。随着 7.0 发布,我们也将恢复到新特性的开发,人体工程学的提升,更棒的性能以及实现一个新的 API 来支持广泛的生态系统。虽然这个功能似乎影响很大,但我们预计和先前版本到 TypeScript 7.0 所花费的时间大致相同,也就是 3-4 个月发布带有新特性的版本。随着 TypeScript 7.1 即将发布,我们希望可以减少迁移带来的影响,让社区稳步前进。
我们也鼓励你分享使用 TypeScript 7.0 的体验。可以关注 Bluesky 上的 @typescriptlang.org 或者 Mastodon 上的 @[email protected] ,或者 Twitter 上的 @typescript 并添加相关 tag ,向我们以及其他人分享你对 TypeScript 7 的看法。
我们深知这个版本会对 TypeScript 生态系统产生难以致信的价值。我们希望它可以让你每天的代码体验,更快,更有趣,更高的生产力,更加专注其中。
欢迎来到 TypeScript 原生工具集的时代。
祝你编程愉快!
—— TypeScript 团队敬上。
后记
希望快快支持 Vue ~~