TypeScript 的瓶颈不在类型,在工具链:2026 大型项目类型检查优化实践
TypeScript 的瓶颈不在类型,在工具链
一个越来越普遍的现实:大型 TS 项目里,
tsc一次全量检查要几分钟,而 bundler 打包只要几秒。
类型检查比转译还慢——这不是语言的问题,是一条 2012 年设计的单通道检查器撞上了 2026 年的工程规模。
今年围绕”给类型检查提速”的讨论和工具链换挡,值得做一次系统梳理。
一、先诊断,再吃药
所有优化文章都该从这一节开始,因为大部分团队改了一堆配置,从没看过编译器到底慢在哪。
1 | # 生成 trace,用 chrome://tracing 打开,看每个类型别名/接口花了多少毫秒 |
--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——看不见瓶颈的人,没资格谈优化。
信息来源
- Unlocking Advanced TypeScript Performance Optimization Techniques — Developers Heaven
- TypeScript Best Practices for Large-Scale Apps in 2026 — Nordiso
- TypeScript 性能优化:避免类型检查拖垮大型项目 — PlumePHP
- How TypeScript Prepares You for Performance Optimisation Evolution — TechnoSports
- TypeScript Expert Skill — Skillselion
评论