配了三年 monorepo,今天才发现 .d.ts 从来不是为并行设计的——isolatedDeclarations 把这件事彻底原生化了
monorepo 里最贵的等待是什么?
不是 npm install,现在 pnpm 已经很快了。也不是代码编译,esbuild 早把这件事压到秒级了。真正的拖油瓶是 TypeScript 的类型声明生成:每个包的 .d.ts 都要等它所有依赖的 .d.ts 先跑完,整个链路是串行的,跟 CPU 核心数没关系,只跟依赖深度成正比。
这个问题 TypeScript 团队其实一直知道。2025 年他们说”我们在修了”,2026 年 Go 重写的编译器说要解决这件事——但真正落地的,是 TypeScript 5.8 里一个叫 isolatedDeclarations 的 flag。
它改了什么
isolatedDeclarations 打开之后,TypeScript 要求所有 export 出去的函数、变量、类必须有显式类型注解。这个要求本身不新鲜,但它的副作用才是重点:有了显式注解,.d.ts 生成不再需要完整类型推导了——它只要把注解本身抄出来就行。
这意味着每个文件的声明文件可以独立生成,不再需要等依赖链。 oxc、swc、esbuild 这些工具可以并行处理各自包的声明文件,不需要把整个 monorepo 喂给 tsc。
实测数据是硬道理
有人在 GitHub 上贴了一个真实 monorepo 的对比:47 秒的构建,打开 isolatedDeclarations + composite + project references 之后,变成 3.2 秒。另一位说 CI pipeline 快了 8 倍。多数团队的回报率在 3x 到 15x 之间,取决于依赖深度和包的数量。
这个代价是,你要多写一些类型注解。以前可以写 export function getUser(id) { return { ... } },现在要写成 export function getUser(id: string): User { return { ... } }。函数返回类型、const 变量类型、泛型参数都要补上。第一眼觉得烦,但换来的是本地构建和 CI 都不再卡在类型声明这一步。
三步开始用
第一步,在根 tsconfig.json 和各 package 的 tsconfig 里加 "isolatedDeclarations": true 和 "composite": true。如果已经有 project references,这一步最简单。
第二步,跑 tsc –noEmit 找所有报错。报错的就是缺少显式注解的 export,逐个补上类型就行。这件事没法偷懒,但每个报错都是真实的技术债——这些类型本来就应该写清楚。
第三步,把新的 tsconfig 加进 CI。第一次跑会慢一些,之后每次 PR 的类型检查和构建就开始享受并行红利了。
一个提醒:这个 flag 不能只开一半。开了之后 tsc 会对所有文件要求显式类型,部分迁移反而会更乱。要么全开,要么先不开。
现在 monorepo 里跑 tsc –build 看着那个进度条一点一点往前走的团队,可以花一个下午把这件事做了。值回的时间从下一次 CI 开始算。
评论区
登录后可评论。