这节在地图里是"让契约跑得动"的工程层。第六章讲了慢在类型检查阶段,这一节给具体提速开关与组织结构。契约再好,若保存要等十秒,团队也会偷偷关掉它——所以速度本身就是正确性的保障。
// tsconfig.json { "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo" } }
第二次运行只重查改动文件及其依赖。对"改一行全量重查"的项目,这是性价比最高的一招。注意 tsBuildInfoFile 要放 node_modules 或进 gitignore,别提交缓存。
references 让子项目各自有独立编译信息,互相只消费 .d.ts,不整体重查。
// 根 tsconfig.json( solution 模式) { "files": [], "references": [ { "path": "./packages/core" }, { "path": "./packages/api" } ] }
# 只构建变更的子项目 npx tsc -b # build 模式,按依赖图增量构建
api 改了不影响 core 的重新检查,-b 模式按图调度。这把"一次检查全仓"变成"只查动过的包"。
{ "compilerOptions": { "skipLibCheck": true } }
第三方 .d.ts 内部不查,能省大量时间。前提是信任那些声明(热门库基本可靠)。第六章强调过:别盲目开,先确认瓶颈在第三方声明(用 --extendedDiagnostics 看 Check time)。
编辑器实时标红若卡,可让语言服务用 transpileModule 模式(只转译不全局检查)做最快反馈,全局检查交给保存时或 CI。
// .vscode/settings.json { "typescript.tsserver.useSeparateSyntaxServer": true }
这张图把四招对应的"提速层面"分层:

# 1. 看耗时分布 npx tsc --extendedDiagnostics # 2. 看实际编译哪些文件(揪误包含的大文件) npx tsc --listFiles | wc -l # 3. 看模块解析路径 npx tsc --traceResolution > log.txt
背景:项目检查要 40 秒。操作:先跑诊断。结果:发现 include 把 dist 也包进来了。解读:收窄 include 比上任何缓存都直接。变式:超大单文件用 @ts-nocheck 临时隔离(应急,别常驻)。
很多人以为"快就得关检查"。其实 incremental/skipLibCheck/项目引用都是"少做无用功",不降低对你自己代码的严格度。真正该警惕的是用 any 或关 strict 换速度——那是砍契约,不是提速。
迁移老项目时,全量错误太多,可用 // @ts-expect-error 标记"已知待修"的单点,让 CI 允许通过、同时防止误删。
// @ts-expect-error 旧逻辑类型未补,issue #123 跟进 const total = legacyCalc(a, b) as number; // 若以后这行真的修对了,@ts-expect-error 会因"无错可压"而自己报错, // 提醒你删掉注释——这是它比 @ts-ignore 安全的地方
输入输出:标了 expect-error 的行若真的没错误了,编译器反而报"未使用的 ts-expect-error",逼你清理。对比 @ts-ignore 永远沉默,expect-error 更适合"遗留债务"场景。注意:只能压单点、带 issue 编号,别用它当 as 的替代品。
检查慢常因把 dist、coverage、测试桩也拉进 Program。用 exclude 把它们挡在门外。
{ "compilerOptions": { "rootDir": "src" }, "include": ["src"], "exclude": ["node_modules", "dist", "coverage", "**/*.test.ts", "src/**/*.stories.ts"] }
输入输出:排除后,tsc 建立的 Program 只含 src 真源码,文件数大幅下降,Check time 随节点数线性下降。配合 --listFiles 可核对实际参与的文件清单,避免"以为排除了其实没排除"。解读:这是性价比极高的第一刀——先让检查范围正确,再谈缓存与增量。
第六章说过泛型过度实例化是头号瓶颈,这里给出量化验证手法,改完能看出是否真有效。
# 改前 npx tsc --extendedDiagnostics 2>&1 | grep -E "Instantiations|Check time" # 例如:Instantiations: 520000 / Check time: 28.4s # 收敛一个被滥用的大泛型后 npx tsc --extendedDiagnostics 2>&1 | grep -E "Instantiations|Check time" # 例如:Instantiations: 90000 / Check time: 6.1s
背景:有人写了 DeepMerge<AllConfig, Partial<AllConfig>> 被几百处实例化。操作:改用浅合并 + 显式字段。结果:Instantiations 掉一个数量级,Check time 同步下降。解读:性能优化不能凭感觉,"改前改后数字对比"才是闭环——这和本节的"先度量再修"完全一致。
verbatimModuleSyntax 减少模块图负担开启 verbatimModuleSyntax 后,类型导入必须显式 import type,编译器不必再"猜"某个 import 是值还是类型,能更早裁剪模块依赖图,对超大项目有可观提速。
{ "compilerOptions": { "verbatimModuleSyntax": true } }
import type { User } from "./models"; // 类型:编译器知其无运行时副作用 import { saveUser } from "./api"; // 值:照常保留
背景:大型项目里"到底是值还是类型"的判断要扫整个图,开销累积明显。操作:开启开关 + 用 import type。结果:模块图更干净,检查更快。解读:它和第四章的语义收紧同源,顺带给速度——又一次"严格度与性能同向"的例证,而非取舍。
isolatedModules 配合单文件转译isolatedModules 要求每个文件可独立转译(不依赖跨文件类型信息),这恰好让 esbuild/transpileModule 等单文件工具能安全处理,避免了"为类型信息必须全局分析"的开销。
{ "compilerOptions": { "isolatedModules": true } }
// 开启后,重新导出类型必须显式 import type export type { User } from "./models"; // OK // export { User } from "./models"; // 报错:User 是类型,需 type 前缀
背景:想用 Vite/esbuild 做极快单文件转译。操作:开 isolatedModules 并规范类型导入。结果:转译层不必全局分析,构建分离更彻底(呼应第四章构建集成)。解读:它对"检查/转译职责分离"是强约束,间接让检查范围更可控。
--watch 与编辑器语言服务的智能增量长期运行的检查进程比"每次冷启动"快得多,因为它把符号图常驻内存,watch 模式只增量重查改动。
# 开发时启动常驻检查,文件改动即增量重查 npx tsc --watch --noEmit # 编辑器语言服务本就是常驻进程,故实时标红比命令行冷跑快
// 让语言服务独立语法服务器,先极速标红、再后台全量检查 { "typescript.tsserver.useSeparateSyntaxServer": true }
背景:冷启动要建立整个 Program(扫描所有文件),耗时可观。操作:常驻 watch/语言服务。结果:后续改动只增量。解读:常驻进程把"建图成本"摊到启动一次,后续每次改动边际成本极低——和 incremental 缓存是两条互补路径。
.tsbuildinfo 与依赖本地增量靠缓存文件,CI 每次全新拉取会丢缓存。把 .tsbuildinfo 与 node_modules 在 CI 间缓存,让"首次检查"也享受增量。
# CI 配置(伪代码) cache: - node_modules - node_modules/.tmp/tsconfig.tsbuildinfo steps: - run: npm ci - run: npx tsc -b # 有缓存则只查变更
背景:CI 上"每次从零"让大仓检查动辄几分钟。操作:缓存构建信息文件。结果:CI 也走增量,时长从分钟级降到秒级。解读:本地快、CI 慢的常见根因就是"缓存没带过去",把 tsBuildInfoFile 纳入缓存即对齐两端体验(详见第四章 CI 集成)。
不是八招全上,按"性价比与风险"排个序,先落低风险高收益的:
第一梯队(低风险、立竿见影): 1. exclude 收窄 include 范围 2. incremental 缓存符号图 3. skipLibCheck 跳第三方(先诊断确认) 第二梯队(结构改造,需规划): 4. 项目引用 + tsc -b 拆大仓 5. CI 缓存构建信息 第三梯队(语义收紧,顺带提速): 6. verbatimModuleSyntax + import type 7. isolatedModules 8. 收敛过度泛型(第六章诊断驱动)
背景:有人一上来就拆项目引用,工程量大还容易出错。操作:先吃第一梯队三招。结果:多数项目到这就够了。解读:优化讲"先摘低垂果实",架构级改造留到有明确瓶颈时再做。
tsc -b 让大仓按包增量⚠️ 别把"编译慢"直接归咎于 TS 本身。多数情况是 include 过宽、单文件过大、或重复全量检查;先诊断再优化,别轻易用 any 妥协。
💡 大仓项目优先上"项目引用 + tsc -b",比全局 incremental 更准——它按包粒度调度,改动一个包不会触发全仓重查。