把 typescript 换成 typescript-native-bridge:tsc、vue-tsc、tsserver 原样跑在 Go 版 tsgo 上
以 npm 包 typescript-native-bridge(TNB)发布。它是一个 drop-in 的 typescript 替代品:把 typescript 这个包换成这个 fork,tsc、vue-tsc、svelte-check、astro-check、glint、ESLint 和编辑器都照旧用,只是 checker 从 JavaScript 变成在进程内跑的 tsgo(微软用 Go 重写的 TypeScript 编译器)。没有新 CLI、没有新 LSP、不用给每个工具单独配、不改业务代码。
- 仓库:https://github.com/johnsoncodehk/typescript-native-bridge (Apache-2.0,Microsoft TypeScript 与 microsoft/typescript-go 的衍生作品)
- npm:https://www.npmjs.com/package/typescript-native-bridge
为什么不能直接用 TypeScript 7(tsgo)
typescript@7 是微软的 Go 原生重写,但它塞不进你实际在用的那些工具:
vue-tsc/astro-check/svelte-check/glint建在经典 typescript 编程接口上(createProgram、Volar hooks、自定义 host)。v7 的编程接口是新的 tsgo API,不是经典接口的 drop-in 替代,这些工具没法直接迁过去。- ESLint(typescript-eslint)import 的是经典 typescript API,调用
getTypeChecker()—— 同样是 API 不匹配。 - 编辑器跑的是 tsserver + Language Service Plugins(
.vue靠@vue/typescript-plugin),tsgo 的 LSP 不支持这套插件模型。
TNB 保留经典包接口,把 v7 引擎(tsgo 7.x)以进程内方式放在它后面——所以一次 typescript override 就能同时加速上面所有工具。
安装
pnpm(monorepo):
# pnpm-workspace.yaml
overrides:
typescript: npm:typescript-native-bridge@
pnpm install
pnpm exec vue-tsc -b --noEmit
如果包是通过 catalog: 依赖 typescript 的,catalog 条目也要一起改。
npm:
{
"devDependencies": { "typescript": "npm:typescript-native-bridge@" },
"overrides": { "typescript": "$typescript" }
}
按上面这样写 alias 和 $typescript override 引用——把 npm:typescript-native-bridge@… 直接写进 overrides 里,某些 npm 版本会拒绝或者解析错(issue #8)。版本必须精确(例如 6.0.3-bridge.6.tsgo.7.0.2 —— caret range 匹配不上 prerelease),或者用 latest dist-tag。
yarn:resolutions 里写 typescript: npm:typescript-native-bridge@。
本地路径:overrides 里写 typescript: link:../typescript-native-bridge(必须先构建,需要 Go,跑 npm run setup)。
任何 override 改动之后都要重装。override 是全仓库生效的。
确认生效
进程内第一次类型检查时,TNB 会往 stderr 打一行暗色文本:▎ TNB ACTIVE — typescript is the tsgo-backed fork。没有这行 banner,说明加载的还是原版 typescript。
node -e "console.log(require.resolve('typescript'))"
已验证兼容的工具
| 工具 | 状态 | 验证环境 |
|---|---|---|
| tsc | ✅ | compiler test corpus |
| vue-tsc | ✅ | elk.zone monorepo(约 2,000 文件):报错与 stock 一致,约快 3× |
| astro-check | ✅ | fixture 项目:输出与 stock 相同 |
| svelte-check | ✅ | fixture 项目:输出与 stock 相同 |
| glint | ✅ | fixture 项目:错误集与 stock 相同 |
| mdx-tsc | ✅ | 诊断输出与 stock 相同 |
| ESLint + typescript-eslint(type-aware) | ✅ | 1,000 文件语料:lint 输出与 stock 逐字节一致 |
| tsserver + @vue/typescript-plugin | ✅ | volar language-tools 测试套件:205/209 通过 |
CI gate 会回放 LS probe 语料(约 19k 单元)。
框架相关:.vue / .svelte / .astro / .mdx / .gts 走标准 extraFileExtensions 契约。host 注入的 virtual content 能到达 Go checker。当 host 提供了额外扩展名时,allowArbitraryExtensions 推断为 true。不支持的情况:自定义 resolveModuleNames / resolveModuleNameLiterals 把某个 import 重映射到另一个物理文件(bridge 是同步 JS→Go,tsgo 无法回调 JS resolver)。
性能(Apple Silicon,请以自己的仓库为准)
| 负载 | Stock | TNB |
|---|---|---|
| vue-tsc -b 全量检查(elk.zone,约 2,000 文件) | 9.7s | 3.2s(约 3×) |
| type-aware ESLint 单次运行(1,000 文件) | 2.3s | 2.4s(+1.5%) |
| 同上,3,000 文件 | 6.9s | 6.8s(约持平) |
| JS heap 峰值(1,000 文件 ESLint) | 769MB | 631MB(−18%) |
| 整进程 RSS 峰值(vue-tsc -b) | 1.8GB | 3.3GB(约 1.9×,结构性的) |
规律:时间花在 checker 上的地方,TNB 就更快。vue-tsc 的 whole-program 语义 pass 从约 5.5s 降到约 1.5s;TNB 的 thin program 惰性物化文件。例外是单次 type-aware ESLint:时间花在 parsing / AST 转换 / 规则执行上,每 1,000 文件约 44K 次 checker RPC 都压在 JS↔Go 边界上——这是持平,不是收益。内存收益出现在长时间会话的编辑器路径上。编辑器路径用 V8-arena transport,每个请求约 1.0 次 bridge RPC:quickinfo p50 0.16ms,completionInfo 0.23ms,references 1.5ms。
编辑器 / tsserver(VS Code、Cursor)
CLI 会自动用上 TNB,编辑器不会。
- Workspace settings:
{
"js/ts.tsdk.path": "node_modules/typescript/lib",
"js/ts.tsdk.promptToUseWorkspaceVersion": true
}
- 命令面板 →
TypeScript: Select TypeScript Version→Use Workspace Version - 验证:版本选择器里显示的是
node_modules/typescript/lib下的路径;Output → TypeScript 通道可能打出 TNB ACTIVE。Vue/Nuxt 用户继续在 tsconfig 的compilerOptions.plugins里保留@vue/typescript-plugin。
行为,以及与 tsgo 的差异
checker 的行为是 tsgo 7.0.2 的,不是原版 TypeScript 6.0.3 的——从 stock 迁移过去,就意味着继承 tsgo 的诊断、内置 lib 和显示输出。TNB 自己对 tsgo 行为的改动是有清单、并由 CI 强制的;例如 getTypeFromTypeNodeWorker 解析类型位置的实体名(issue #30);extra-file-extensions 的处理让 .vue 文件产出 Button.vue.d.ts,而不是 tsgo 的 Button.vue.d.vue.ts(issue #63);whole-program 诊断的确定性(issue #42/#51)。任何看起来与 stock 6.0.3 有差异、又不在清单里的行为,都是 tsgo 本身的行为。
平台支持
bridge 二进制以按平台划分的 optional deps 分发:@typescript-native-bridge/darwin-arm64、darwin-x64、linux-x64、linux-arm64、linux-arm、win32-x64、win32-arm64。Linux 目标为 glibc 2.35。
Alpine/musl 不支持:Go 的 -buildmode=c-shared 运行时在 musl libc 上加载即崩溃(golang/go#13492,一个开了十年、正在修的问题,修复见 golang/go#75048);tsgo 自己的 CLI 能在 Alpine 上跑,只是因为它发的是不带 CGO 的静态二进制。变通做法:在 node:24 或 node:24-bookworm-slim 里跑 typecheck/lint,再部署进 Alpine stage;apk add gcompat 不起作用。
在不支持的平台上会报 unsupported platform or missing optional dependency——那就从源码构建(带 submodule clone,跑 npm run setup,需要 Go + C 工具链)。
排障与回滚
- 没有 banner:检查 workspace 根目录的 override;pnpm 11 要把
package.json→pnpm.overrides挪到pnpm-workspace.yaml的overrides;catalog 和 overrides 都要更新;重装。 - CLI 正常、编辑器不正常:补上 tsdk 设置。
- 调试慢运行:
TSGO_PROFILE=1,退出时会打印[tsgo-profile]的 RPC/耗时汇总。 - 卸载:删掉 override,重装,
require.resolve('typescript')回到 stock 6.x。不需要改任何源码。