一句话看懂
项目地址:https://github.com/pingdotgg/ts-rust 
ts-rust 把 TypeScript 的类型检查器从 Go 搬到 Rust,行为上尽量跟官方 tsc 保持一致。它不是重新设计检查算法,而是逐行移植微软的 typescript-go 实现。README 写得坦率:作者花了超过 42 万美元的 token 做这件事,自称"我从未阅读过代码",稳定性完全靠自动化测试兜底。181711 个移植过来的 Go 测试全部跑通,这是目前对兼容性最硬的证据。
它解决什么问题
TypeScript 项目越大,tsc 类型检查就越慢。官方的解法是用 Go 重写编译器(typescript-go,提速明显),但 Go 版本不是所有场景下最快的选择,而且集成到 Rust 系工具链(WASM、嵌入式环境)会有语言边界摩擦。ts-rust 把 Go 实现再搬一次到 Rust 里:一是 Rust 二进制可以直接嵌进 Rust 项目或编译成 WebAssembly,不用再启动 Node.js 进程;二是实测检查速度比 Go 版本还快一截。代价是目前只覆盖两个平台,而且是个刚起步、作者自称没读过代码的项目。
核心概念速览
tsc-rs :ts-rust 对外发布的 npm 包名,用户真正会用的命令行工具。接受和 tsc 一样的参数(比如 -p 指定 tsconfig 文件),可以理解成"tsc 的 Rust 替身"。
typescript-go:微软官方用 Go 重写的 TypeScript 编译器,是 ts-rust 移植的直接上游源。ts-rust 对着这个 Go 版本移植而非原始 JS/TS 源码,继承的是 Go 版本的算法和行为细节。项目把移植锚定在 typescript-go 的某个具体提交(673a5f17d713)上,相当于钉死了版本基线,不会随上游持续追新。
goport :在这个项目里指"把 Go 代码移植成 Rust 代码"这件事本身,体现为核心 crate 叫 ts_goport。编译器的检查器、绑定器、AST 等核心逻辑全部在这个 crate 里。
Effect 诊断:Effect 是 TypeScript 生态里一个函数式编程库,有自己的语言服务插件做额外类型提示和报错(诊断码 377xxx 号段)。ts-rust 把这套诊断规则直接内置进了检查流程,用 Effect 的项目不需要额外装插件就能拿到对应诊断信息。
架构拆解

仓库模块划分跟它"移植"的性质高度吻合,基本按 Go 源码的功能分区对应过来。核心是 crates/ts_goport,又拆出两个子 crate:goport_util(通用工具函数)和 goport_lsproto(语言服务协议)。ts_goport 内部按功能分成 checker(类型检查器)、binder(符号绑定)、ast(语法树结构)、api(对外接口和会话管理)几大块。
另外两个独立 crate 值得注意:ts_wasm 负责把整个编译器编译成 WebAssembly,是让 ts-rust 能在浏览器或在线 IDE 里跑起来的关键;tools/ts_ast_codegen 用来生成 AST 相关数据结构代码,还有一个承担类似职责的 ts_diagnostics_codegen,生成诊断信息相关目录结构。这种"代码生成器生成样板代码,核心 crate 负责逻辑"的组合,在体量巨大的移植项目里能避免手工维护大量机械重复的类型定义。
依赖关系上 ts_goport 是绝对中心:两个代码生成工具给它生成代码,它依赖两个子 crate 提供工具函数和协议支持,最终能被编译进 ts_wasm 产出 WASM 构建。整体是"单核心 + 周边支撑"结构,符合它作为编译器而非分布式系统的定位。
关键实现走读
先看 checker 模块的组织方式:
rust
//! Go package `checker`. One file per ported Go range.
pub mod checker_p01;
pub mod checker_p02;
pub mod checker_p03;
pub mod flow_p1;
pub mod flow_p2;
pub mod flow_p3;
pub mod flow_skip;
pub mod jsdoc;
pub mod jsx_p1;
pub mod jsx_p2;
pub mod relater_p1;
pub mod relater_p2;
pub mod services;
pub mod types;
这是 checker 模块入口文件,罗列几十个子模块:checker_p01到p03按序号分段,flow_p1到p3加一个flow_skip单独处理,jsx_p1、p2、relater_p1、p2也两两分段。注释直接写明"一个 Go 源码范围对应一个文件"。
为什么这样写:TypeScript 的类型检查器在 Go 版本里原本是几个巨大单文件(动辄几万行),直接整体翻译成一个 Rust 文件既不利于编译速度,也不利于核对------移植时需要反复对照原始 Go 代码某段逻辑是否被正确翻译。按原文件代码区间切成多个小文件,相当于建立了一张可追溯的映射表,核对调试时能快速定位。
没有它会怎样:如果塞进单体文件,几万行 Rust 代码混在一起,无论人工审查还是 AI 辅助生成,都很难验证某段逻辑是否完整准确对应原始实现。考虑到项目依赖大量 AI 生成代码且作者自称没读过代码,这种切分方式是弥补"无人审查"风险的结构化手段。
再看 api 模块的 prelude 设计:
rust
/// Glob import for api files: `use crate::api::prelude::*;`.
pub mod prelude {
pub use super::{
callbackfs::*, module_resolution::*, proto::*, protocol_msgpack::*, server::*,
session_p1::*, session_p2::*, stringer_generated::*,
};
pub use crate::api::encoder;
pub use crate::astnav;
pub use crate::frontend::bundled;
pub use crate::frontend::json_ext::{self, LspAny};
pub use crate::frontend::vfs::osvfs;
pub use crate::frontend::{compiler, tsoptions, tspath, vfs};
pub use crate::gostd::{self, Context, GoError};
pub use crate::ipc;
pub use crate::jsonrpc;
pub use crate::locale;
pub use crate::ls::{self, lsconv};
pub use crate::lsp::lsproto;
pub use crate::prelude::*;
pub use super::proto::{PackageId, ResolutionMode, ResolvedModule};
pub use crate::program::ls_program;
pub use crate::project;
}
定义了 prelude 模块,把 api 相关文件会用到的几十个类型和函数统一重新导出,其他文件只要写一行 use crate::api::prelude::*; 就能拿到所有依赖。
为什么这样写:从列表能看出 api 层要同时打通编译器核心、项目管理、文件系统抽象、跨进程通信、LSP 协议,甚至 Go 标准库的模拟层(gostd::{Context, GoError})。api 是整个系统里连接面最广的一层,等同于总线。用 prelude 模式集中管理这些导入,减少每个文件开头冗长的 use 语句,也把"api 层依赖哪些子系统"写成了一份清单,改动时容易看出影响范围。
没有它会怎样:没有统一导出,api 下每个文件都要单独写十几行 import,底层模块路径变化时需要去改散落各处的 import 语句。对于靠 AI 持续生成修改代码的项目,减少分散改动点能降低遗漏概率。
值得注意的一处细节是 gostd 模块里的 GoError 和 Context------这两个明显是对 Go 语言 error 处理和 context 机制的模拟,说明移植过程并没有把 Go 语义改写成 Rust 更地道的写法,而是直接搬运了 Go 的概念模型。这符合项目"保持原算法和行为"的目标,但也意味着 Rust 代码里会长期带着一层 Go 的"口音"。
动手上手

安装:
bash
npm install -D tsc-rs
执行后 node_modules 里会多出 tsc-rs 包,可以用 npm ls tsc-rs 验证安装成功。
运行类型检查:
bash
npx tsc-rs -p tsconfig.json
这条命令跟平时跑 npx tsc -p tsconfig.json 的用法完全一样。如果项目没有类型错误,预期命令正常退出(退出码 0);如果有类型错误,会打印出跟 tsc 风格一致的错误信息。可以把输出跟同一项目下 npx tsc -p tsconfig.json 的输出做对比,检验兼容性------这也是 README 反复强调要用户自行验证的点。
要注意当前只提供 Linux x64(静态编译)和 macOS arm64 两个平台的原生二进制,Windows 或 Linux arm64 会因为找不到对应二进制而失败。
应用场景
CI 流水线里替换 tsc :类型检查往往是构建流程里耗时较长的一步。装好 tsc-rs 后用 npx tsc-rs -p tsconfig.json 替换原来的 tsc 调用,如果 CI 跑在 Linux x64 环境下,理论上能拿到速度提升,但要先确认运行环境在支持列表内。
Effect 项目的类型检查 :如果项目用了 Effect 库并配置了对应 language service 插件,tsc-rs 能直接输出 377xxx 号段的 Effect 专属诊断,不需要额外装插件。但这里只是把诊断规则搬过来了,Effect 插件在编辑器里的其他交互功能(比如特定快速修复)并没有一起移植。
WASM 场景下的类型检查 :ts_wasm crate 把编译器编译成了 WebAssembly,理论上可以嵌入基于浏览器的在线 IDE 做实时类型检查,不依赖 Node.js 环境。不过具体怎么调用这个 WASM 产物、暴露了哪些接口,研究材料里没有展开,需要用户自己翻源码确认。
独立分析

这个项目最值得琢磨的不是技术本身,而是它暴露出的 AI 辅助编程成本结构。而用另一个模型(Opus 5.5)从零开始,10 小时内就做出了一个可用初版。这个对比说明,在"把现成逻辑忠实搬运到另一种语言"这类任务上,模型选择和调用策略带来的效率差异可能比堆算力更关键------砸钱堆 token 不一定线性换来兼容性提升。
作者自称"我从未阅读过代码",这句话需要认真对待,不是自谦。181711 个移植测试全部通过是目前质量的唯一硬证据,但测试覆盖率再高也不能完全替代人工代码审查,尤其是边界情况、性能相关路径、安全敏感逻辑(文件系统访问、跨进程通信)上。研究材料提到某些 monorepo 场景下 TS6059 诊断码的报告结果跟 tsc 不一致,这类问题恰恰是测试集之外、靠人工审查才容易发现的类型。对于把这个工具接入生产 CI 的团队,不能把"181711 测试全过"直接等同于"生产可用",需要在自己的真实项目上做一轮完整对比验证。
性能数据有参考价值------在 60 个开源项目上,类型检查时间大约是 Go 版 typescript-go 的一半(几何平均)。但对比材料同时提到 bun 自带的 bun check 在 T3 Code 这个测试里跑得更快,只是它不包含 Effect 诊断。这说明"更快"要看跟谁比、比什么------跟 Go 版比确实有提速,但如果已经在用 bun 生态且不需要 Effect 诊断,bun check 可能依然是更直接的选择。性能数字不能脱离具体场景单独看。
局限与风险
平台覆盖是最直接的限制,目前只有 Linux x64(静态编译)和 macOS arm64 两种原生二进制,Windows 用户和 Linux arm64(树莓派或某些云厂商 ARM 实例)暂时用不了。
编辑器集成功能不完整,快速修复、代码重构、鼠标悬停提示这些依赖语言服务器深度交互的功能没有一起移植,目前更适合当命令行检查工具用,不是直接替换编辑器里的 TypeScript 语言服务插件。
兼容性边界上,材料提到某些 monorepo 场景下 TS6059 诊断码(通常跟 rootDir 配置相关)的报告结果跟官方 tsc 不一致,说明复杂项目结构下不能假设 100% 行为对齐。另外 tsc-rs --version 显示的是它移植自的 TypeScript 版本号,不是这个 npm 包自身的版本号,容易在排查问题时造成误判。
更底层的风险在于项目开发方式本身。移植基线钉死在 typescript-go 的某个具体提交上,不会随官方修复自动同步,官方后续修了什么 bug、加了什么特性,ts-rust 需要重新移植才能跟上。再加上作者自述没有读过代码,长期维护质量很大程度上依赖持续的自动化测试和社区的实际使用反馈,这是一个需要持续观察的早期项目。
结论卡片

适合谁:需要在 Linux x64 或 macOS arm64 环境下跑高性能 TypeScript 类型检查的团队,尤其是看重 CI 速度、或需要把类型检查嵌入 Rust/WASM 技术栈的开发者。用 Effect 库的项目也能直接受益于内置的诊断支持。
不适合谁:依赖 Windows 或 Linux arm64 平台的用户目前用不了;重度依赖编辑器高级语言服务功能(快速修复、重构、悬停提示)的场景,这个工具还补不上。
下一步建议:这是个早期项目,在接入正式流程前,务必先用自己项目的 tsconfig 跑一遍对比测试,把 tsc-rs 和官方 tsc 的输出结果逐项核对,确认没有不可接受的行为差异,再考虑在 CI 里替换。