TypeScript 的瓶颈不在类型,在工具链

一个越来越普遍的现实:大型 TS 项目里,tsc 一次全量检查要几分钟,而 bundler 打包只要几秒。
类型检查比转译还慢——这不是语言的问题,是一条 2012 年设计的单通道检查器撞上了 2026 年的工程规模。
今年围绕”给类型检查提速”的讨论和工具链换挡,值得做一次系统梳理。

一、先诊断,再吃药

所有优化文章都该从这一节开始,因为大部分团队改了一堆配置,从没看过编译器到底慢在哪。

1
2
3
4
5
# 生成 trace,用 chrome://tracing 打开,看每个类型别名/接口花了多少毫秒
npx tsc --generateTrace ./trace

# 总览:检查耗时、文件数、行数、AST 节点数
npx tsc --extendedDiagnostics --incremental false | grep -E "Check time|Files:|Lines:|Nodes:"

--generateTrace 的输出会在 chrome://tracing 里画出一张火焰图,top 5 慢模块一目了然。这个诊断习惯本身比任何一条优化技巧都值钱——它把”我觉得类型系统不行了”变成”第 3 号联合类型的实例化占了 40% 检查时间”。

点评。 编译时长是一个应该被持续跟踪的工程指标,和构建产物体积、接口 P99 一样。给 CI 加一行 check time 输出,比装十个”提速秘籍”都实在。这个思路对任何工具链都成立——包括本站的 Hexo 构建:我同样是先 npx hexo generate 计时,再决定要不要动模板。

二、三档提速策略的真实收益

社区今年对比最充分的三个方向,结论出奇一致:

策略 收益 代价
path aliases + 模块解析优化 明显(对未用 Monorepo 的团队) 配置复杂、IDE 奇癖、运行时需 tsconfig-paths;Monorepo 团队提升边际
动态 import 代码分割 首屏包体下降,类型检查可按路由拆 异步复杂度、加载态;SSR 场景过度使用反而伤 TTI
--incremental 增量构建 性价比最高,只重检变更文件 CI 脏缓存可能造成”假通过”,缓存体积随提交增长

增量构建是共识里的第一名,但它的坑要说透:CI 里复用了本地旧缓存,会产生 false pass。对策也简单——CI 缓存 key 必须包含 lockfile 哈希,且发版流水线跑一次 clean build。

点评。 三档策略没有银弹,但排序有启发:收益最大的那个(增量)恰恰是配置成本最低的。这和”框架换不换”的争论形成对照——提速的第一步从来不是换框架,是先量再改配置。

三、tsconfig 清单:便宜的那一半

不改变项目结构就能做的一批设置:

  • **skipLibCheck: true**——跳过 node_modules 里的声明检查,大项目提速明显。注意它也会掩盖应用层引用库类型的错误,折中方案是”库检查”和”应用检查”分成两个 CI 任务;
  • **incremental: true + 指定 tsBuildInfoFile**——缓存编译信息,改动只重检增量;
  • **noUnusedLocals / noUnusedParameters**——顺带治理死代码,喂饱 tree-shaking;
  • **import type**——纯类型导入编译后完全擦除,不留模块引用痕迹。对发布 npm 包的库尤其重要;
  • include / exclude 写精确——别让编译器扫测试快照和生成目录。

点评。 这份清单的关键词是”便宜”:每一条都不动架构,半小时能全开。真正的分水岭在下一节。

四、分水岭:把”转译”和”检查”拆开

这是今年工具链最大的一次换挡:用 SWC/esbuild 做转译,把 tsc --noEmit 降级为独立的校验步骤。

  • 转译走 Go/Rust 写的原生工具,本地 HMR 做到亚秒级反馈;
  • 类型检查在 CI 里与 lint 并行跑,快速失败;
  • CI 按分钟计费的时代,构建时间减半直接折算成钱。

代价说清楚:esbuild/SWC 有意不做完整类型检查(那是它们快的根源),所以”生产构建”必须另行挂接 tsc --noEmit,不能省。

点评。 这是对 tsc 的一次”重新定位”:从”编译器”回归”检查器”。而同样的 native 重写浪潮正在席卷整条前端工具链——biome(ESLint+Prettier 的 Rust 替代)、tsgo(TS 的 Go 移植)、rolldown/vite 的 Rust 内核。TS 7 会把”类型检查慢”这个问题从根上了结,但在它稳定铺开之前,”检查与转译分离”就是当下最优解。

五、代码结构:比配置更深的两刀

配置见顶之后,收益在代码组织里:

1. 干掉内部 barrel file(index.ts 再导出)。 2026 年的明确建议:内部模块用直接导入。barrel 制造循环依赖、破坏 tree-shaking、拖慢模块图解析——每个 bundler 都喜欢显式导入图。团队协作别扭一点,换构建性能和清晰度。

2. 类型就近放置,消灭巨型 types.ts。 按领域把类型放在使用它的功能旁边。集中式类型文件是”修改一个接口 → 全仓编译错误雪崩”的元凶;就近放置把影响半径锁在一个模块里。

还有两个具体模式:

  • API 响应类型:显式 interface + type guard,而不是从 schema 推断出的深嵌套泛型。推断很优雅,但语言服务性能差、错误信息难读、运行时校验和静态类型两件事混为一谈;
  • “Type instantiation is excessively deep” 的四个修法:type 交叉换成 interface 继承、超过 100 成员的大 union 拆小、避免循环泛型约束、用类型别名打断递归。

点评。 这两刀切的其实是同一个东西:把”编译器的工作量”当成和”运行时的工作量”一样的成本来管理。运行时我们心疼每一帧,编译时却放任编译器每天晚上全量扫一遍。大型 TS 项目的成熟标志,就是团队里有人盯着 check time 这个数字。

六、IDE 侧:tsserver 不是免费午餐

编辑器卡顿的机理和命令行 tsc 完全不同——tsserver 为每个打开的文件常驻维护 AST 和类型图,内存随工作区规模持续增长。可操作项:

  • typescript.tsserver.maxTsServerMemory 设到 8192 级别,给足内存比让它频繁 GC 强;
  • Monorepo 不要单窗口开全仓,用子目录做工作区根——每个窗口只起一个 tsserver,缩小作用域是降低内存压力最直接的手段;
  • 别拿 tsc --noEmit 的时长评估 IDE 卡顿:前者一次性扫完全部退出,后者在内存里维护增量状态,两个场景。

诊断方法:ps aux | grep tsserver 看内存占用,配合 VS Code 的 tsserver 日志。

七、顺带一轮生态换挡

  • Biome 上位:2026 年它已经是 ESLint+Prettier 的正式竞争者,单工具、单配置、Rust 速度。适合 TS-first、能接受规则数少于 typescript-eslint 的团队;重度依赖自定义规则或 Vue/Angular 的团队继续 ESLint;
  • 声明打包:发布 npm 包的库用 rollup-plugin-dts / api-extractor 把零散 .d.ts 合成单一入口,降低下游解析成本;
  • @types/* 审计:大量类型包已随主库内置,独立的 @types 反而造成声明冲突和重复解析。npm ls @types/* 定期排查。

收尾

今年关于 TS 性能的讨论可以压成一句话:语言没有罪,罪在 2012 年的检查器活在 2026 年的工程规模里。短期解法是拆分职责(转译归转译,检查归检查)+ 增量缓存 + 精确的 tsconfig;长期解法是 TS 7 的原生内核。但在任何方案生效之前,请先跑一次 --generateTrace——看不见瓶颈的人,没资格谈优化。


信息来源

  1. Unlocking Advanced TypeScript Performance Optimization Techniques — Developers Heaven
  2. TypeScript Best Practices for Large-Scale Apps in 2026 — Nordiso
  3. TypeScript 性能优化:避免类型检查拖垮大型项目 — PlumePHP
  4. How TypeScript Prepares You for Performance Optimisation Evolution — TechnoSports
  5. TypeScript Expert Skill — Skillselion