比 TypeScript 快 10 倍的原生 TypeScript
Relative Materials
A 10x Faster TypeScript - Reddit
A 10x faster TypeScript - YouTube
技术痛点
- 扩展性瓶颈:随着代码库规模增长,现有
javascript
实现的typescript
编译器在超大型项目上遭遇明显性能瓶颈。 - 资源消耗过高:大型项目编译和类型检查过程中的内存占用和
cpu
使用率不断增加。 - 开发体验劣化:当项目达到特定规模后,编辑器响应速度、类型检查和代码导航能力显著下降。
- AI 增强需求:现有架构难以支撑高级分析和
ai
驱动的新型开发工具需求。
现有实现的局限性
- 编辑器启动时间过长:
vscode
代码库(150
万行代码)加载需约9.6
秒。 - 命令行编译效率低下: 大型项目类型检查时间经常达到分钟级别。
- 内存利用率不佳:由于
javascript
运行时限制,无法充分优化内存占用。 - 技术栈限制:当前
javascript
实现难以突破性能天花板,需要从语言层面重新思考。
目标
为了改善上述的问题,从根本上解决性能瓶颈,typescript
团队已经开始着手做 typescript
编译器和工具的 原生移植
工作。原生实现将大幅改善编辑器启动时间,将大多数构建时间减少 10
倍,并显著降低内存使用。
通过 移植
当前代码库,我们预计能够在 2025
年中期预览能够进行命令行类型检查的 tsc
原生实现,并在年底前提供完整功能的项目构建和语言服务解决方案。
Go 语言在 TypeScript 引擎重写中的选择理由
To meet those goals, we’ve begun work on a
native port
of the TypeScript compiler and tools.
语言选择评估
我们对多种语言方案进行了全面评估,包括最近的研究和之前的调查。同时考虑了混合方案,即某些组件使用原生语言实现,而保持核心 类型检查算法 在 javascript
中。为此,我们开发了多个原型,使用不同语言探索各种数据表示方法,并深入研究了现有原生 typescript
解析器(如 swc
、oxc
和 esbuild
)的实现方法。
Go 语言优势
需要明确的是,从零开始重写的情况下,许多语言都可能适用。但考虑到当前特定情况的多项标准,go
语言表现最佳,主要原因考虑如下三个方面:
1. 代码兼容性
最重要的考量是保持新代码库与现有代码库的高度兼容性,包括 语义 和 代码结构 方面。我们预计在未来相当长的时间内会同时维护两套代码库。go
语言能够实现结构上相似的代码库,这对于 代码变更的移植 提供了显著便利,开发人员可以轻松地在两个代码库之间迁移变更。
相比之下,那些需要从根本上重新思考 内存管理、可变性、数据结构设计、多态性 和 惰性计算 等方面的语言,虽然可能更适合完全重写,但我们进行的更多是一种 移植 工作,目标是保持现有行为和我们已经构建到 typescript
中的关键优化。go
语言的惯用写法与 typescript
代码库的现有 编码模式 高度相似,这使得移植工作更加可行。
2. 内存管理
go
提供了优秀的内存 布局和分配 控制(对象和字段级别),同时不要求整个代码库持续关注 内存管理 问题。虽然这意味着我们使用了垃圾收集器,但 gc
的缺点在我们的代码库中并不特别明显:
- 我们没有会受
gc
暂停/减速影响的严格延迟限制。 - 批处理编译可以有效地完全避免垃圾收集,因为进程在结束时终止。
- 在非批处理场景中,大部分前期分配(
ast
等)在程序的整个生命周期中存在,并且我们有关于何时是运行gc
的"逻辑"时机的强域信息。
因此,go
的模型在减少代码库复杂性方面为我们带来了巨大收益,同时垃圾收集的实际运行时成本很低。
3. 图形处理能力
我们的代码库中有大量图形处理操作,特别是涉及多态节点的向上和向下遍历树结构。go
在这方面表现出色,特别是在需要与 javascript
版本代码保持相似的上下文中。
弱点与应对方案
go
的进程内 js
互操作性不如某些替代方案。我们有即将推出的计划来缓解这一问题,并致力于提供高性能且符合人体工程学的 js api
。由于当前 api
模型中,消费者可以访问(甚至修改)几乎任何内容,我们在某些可能的优化上受到限制,并希望确保新代码库能够为更改内部表示留出空间,而不必担心影响所有 api
用户。转向更加有意的 api
设计(同时考虑互操作性),将使我们能够在提供这些巨大性能提升的同时推动生态系统向前发展。
为什么不加速 JavaScript
代码?
要加速 typescript
,您需要一种支持 多线程的语言。javascript
实际上只能在 一个核心 上工作。即使有一些即将推出的特性(如 Shared Structs)可以跨线程共享,但这些特性尚未准备就绪。
像 go
和 rust
这样的语言 内置了多线程支持,这意味着它们可以使用多个 cpu
核心来尽可能并行化工作。这就是为什么它们速度更快的原因。
性能提升
我们的原生实现已经能够加载许多流行的 typescript
项目,包括 typescript
编译器本身。以下是在 github
上不同规模的流行代码库上运行 tsc
的时间比较:
代码库 | 代码行数 | 当前版本编译时间 | 原生版本编译时间 | 速度提升 |
---|---|---|---|---|
VS Code | 1,505,000 | 77.8s | 7.5s | 10.4x |
Playwright | 356,000 | 11.1s | 1.1s | 10.1x |
TypeORM | 270,000 | 17.5s | 1.3s | 13.5x |
date-fns | 104,000 | 6.5s | 0.7s | 9.5x |
tRPC (server + client) | 18,000 | 5.5s | 0.6s | 9.1x |
rxjs (observable) | 2,100 | 1.1s | 0.1s | 11.0x |
虽然我们尚未实现所有功能,但这些数据代表了检查大多数代码库时将看到的数量级性能提升。
编译 typescript
自身编译器的开销:
Diagnostics | TypeScript | Native Single Thread | Native Multi Thread(default) |
---|---|---|---|
Files | 217 | 217 | 217 |
Lines | 252851 | - | - |
Identifiers | 425459 | - | - |
Symbols | 260977 | - | - |
Types | 106419 | 111785 | 205491 |
Instantiations | 188269 | - | - |
Memory used | 487981K | 270506K | 332759K |
I/0 read | 0.07s | - | - |
I/0 write | 0.06s | - | - |
Parse time | 0.98s | 0.241s | 0.078s |
Bind time | 0.37s | 0.061s | 0.014s |
Check time | 5.58s | 1.547s | 0.805s |
Emit time | 0.39s | 0.182s | 0.068s |
Total time | 7.31s | 2.031s | 0.966s |
为什么不是 10
倍提升呢?
类型检测器(type checker
) 是 TypeScript
中最重要的部分,它负责检查代码中的类型错误,执行 类型检测器 的编译任务时无法并行化每一个文件,那么就会花费更多的时间,因此上述优化中约 8
倍提升而非 10
倍。
这个原生版本将能够提供整个项目即时且全面的错误列表,支持更高级的重构,并实现之前因计算成本过高而无法实现的深度洞察。这一新基础不仅超越了今天的开发者体验,还将为下一代 ai
工具提供支持,增强开发过程,为能够学习、适应和改进编码体验的新工具提供动力。
编辑器速度
大多数开发时间都花在编辑器中,这也是性能最重要的地方。我们希望编辑器能够快速加载大型项目,并在所有情况下快速响应。像 Visual Studio 和 Visual Studio Code 这样的现代编辑器只要底层语言服务也足够快,就能提供出色的性能。通过我们的原生实现,我们将能够提供极其快速的编辑器体验。
同样以 Visual Studio Code 代码库为基准,在高性能计算机上加载整个项目到编辑器的当前时间约为 9.6 秒。使用原生语言服务后,这一时间将减少到约 1.2 秒,在编辑器场景中项目加载时间提升了 8 倍。这意味着从打开编辑器到在任何 TypeScript 代码库中输入第一个字符,工作体验将更加迅速。我们预计所有项目的加载时间都将看到这一水平的改进。
总体内存使用量也似乎比当前实现减少了约一半,尽管我们尚未主动研究优化这方面,并期望实现进一步的改进。所有语言服务操作(包括完成列表、快速信息、转到定义和查找所有引用)的编辑器响应性也将获得显著的速度提升。我们还将转向语言服务器协议(LSP),这是一项长期的基础架构工作项目,旨在使我们的实现与其他语言更好地对齐。
版本路线图
- 2025 年中期里程碑:提供能够进行命令行类型检查的原生实现预览
- 2025 年底完整发布:交付全功能 TypeScript 7.0 原生实现,包括项目构建和语言服务
- 无缝迁移保障:维护 JS 版本 (6.x) 和原生版本 (7.x) 并行发展,确保生态系统平稳过渡
TypeScript 5.8/5.9:当前最新版本 TypeScript 6.x 系列:基于 JavaScript 的实现,将引入与原生版本对齐的 API 变更 TypeScript 7.0:原生实现的正式发布版本
预计 2025 年中期提供命令行类型检查预览预计 2025 年底完成全功能实现,包括项目构建和语言服务
我们最近的 TypeScript 版本是 TypeScript 5.8,TypeScript 5.9 即将推出。基于 JS 的代码库将继续开发到 6.x 系列,TypeScript 6.0 将引入一些弃用和破坏性变更,以便与即将推出的原生代码库保持一致。
当原生代码库达到与当前 TypeScript 足够的对等水平时,我们将发布它作为 TypeScript 7.0。这仍在开发中,我们将在达到稳定性和功能里程碑时进行公告。
为了清晰起见,我们将简单地称它们为 TypeScript 6(JS 版)和 TypeScript 7(原生版),因为这将是可预见未来的命名方式。您可能还会在内部讨论或代码注释中看到我们提到 "Strada"(原始 TypeScript 代号)和 "Corsa"(此项工作的代号)。
虽然一些项目可能在发布后能够切换到 TypeScript 7,但其他项目可能依赖于某些 API 功能、遗留配置或其他约束,需要继续使用 TypeScript 6。认识到 TypeScript 在 JS 开发生态系统中的关键角色,我们将继续维护 6.x 线的 JS 代码库,直到 TypeScript 7+ 达到足够的成熟度和采用率。
我们的长期目标是使这些版本尽可能紧密对齐,以便您可以在 TypeScript 7 满足您的需求时尽快升级,或在必要时回退到 TypeScript 6。
对于生态的影响
一些针对 typescript
的提速插件在后续的版本中将不再需要。
对于 webpack
的影响
ts-loader 的变革
ts-loader
目前依赖于 typescript
的 javascript
API 来执行类型检查和转译。随着 typescript-go
的引入,将面临以下变化:
需要适配新的编译器 API:
typescript 7
将提供"a new compiler API",这意味着ts-loader
需要重构以支持新的接口。性能提升潜力:
ts-loader
目前的主要瓶颈之一就是typescript
编译速度。如果能集成原生实现,webpack
构建过程中最耗时的部分将得到显著加速。双模式支持: 考虑到
typescript 6 (js 版本)
会继续维护一段时间,ts-loader
可能需要同时支持两种运行模式,类似于:json{ "test": /\.tsx?$/, "use": "ts-loader", "options": { // 新增选项 "compilerImplementation": "native", // 或 'javascript' } }
fork-ts-checker-webpack-plugin 的转型
fork-ts-checker-webpack-plugin
的核心价值在于将 typescript
类型检查移至单独进程,从而加速构建。随着 typescript-go
的 10
倍性能提升:
价值重新评估:
该插件的主要目的是解决
TypeScript
类型检查慢的问题。当原生TypeScript
速度提升10
倍后,分离进程的必要性将大大降低。可能的转型方向:
- 成为
typescript 7
的适配层。 - 提供更高级的功能,如增量编译。
- 优化整合到更简化的工作流中。
- 成为
对于原生工具的影响
下游工具链出于性能考量大多会将 typescript
的 类型检查 和 转译 工作分开。转译工作交付给 原生工具 来完成,类型检查 工作则交付给 tsc
来完成。
对 esbuild
的影响
esbuild
内置了 typescript
转译支持(无类型检查),下游工具链会使用 esbuild
来完成 转译 工作。
例如 vite
的 vite-plugin-typescript
插件会借助 esbuild
的快速转译能力将 typescript
模块转译为 js
模块。
export function esbuildPlugin(config: ResolvedConfig): Plugin {
const filter = createFilter(
include || /\.(m?ts|[jt]sx)$/,
exclude || /\.js$/
);
return {
name: 'vite:esbuild',
configureServer(_server) {
server = _server;
},
async transform(code, id) {
if (filter(id) || filter(cleanUrl(id))) {
const result = await transformWithEsbuild(
code,
id,
transformOptions,
undefined,
config,
server?.watcher
);
}
}
};
}
类型检测工具是采用 tsc
来完成。
{
"name": "vite-react-typescript-starter",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.22.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.26.0",
"vite": "^6.2.1"
}
}
vite
处理 typescript
模块的行为
vite
的工作是尽可能快地将源模块转化为可以在浏览器中运行的形式。为此,vite
建议将静态分析检查与vite
的转换管道分开。这一原则也适用于其他静态分析检查,例如eslint
。vite
使用esbuild
将typescript
转译到javascript
,约是tsc
速度的20~30
倍,同时HMR
更新反映到浏览器的时间小于50ms
。
esbuild
是采用 go
来进行编写的构建工具,由于与 typescript-go
都是 go
实现,技术栈相似,这使得深度集成变得更为可行。
对 swc
的影响
swc
内置了 typescript
转译支持(无类型检查),当 typescript
本身能够提供高性能实现时,swc
在转译 typescript
时的速度优势将不再那么突出。
对 oxc
的影响
oxc
可能需要重新思考其对 typescript
的集成方式。目前,oxc
实现了自己的 typescript
解析器,但这种方式可能存在与官方 typescript
实现不一致的风险。typescript-go
提供了一个高性能的官方实现,oxc
可能会考虑直接集成该实现。