TypeScript monorepo 工程化:类型共享与构建优化
背景与问题界定 随着前端项目规模的扩大,monorepo 已经成为中大型项目的标准组织方式。pnpm workspace + TypeScript + turborepo 的组合方案逐渐成为主流技术栈。但在 monorepo 中使用 TypeScript 面临着几个特有的挑战:首先是类型共享问题——公共类型定义放在哪个包中?如何在保持类型一致性的同时避免循环依赖?其次是构建性能——在包含 20+ 子包的项目中,全量类型检查耗时可达 30 秒以上,极大地影响了开发体验和 CI 流水线效率。再者是发布约束——当 A 包的类型定义引用了 B 包,A 的发布版本如何与 B 的版本保持一致?这些问题如果不在工程化层面系统性解决,monorepo 带来的类型一致性红利很快会被构建和发布摩擦抵消。 目标拆解与工程约束 类型包的独立版本管理:通用类型定义放在独立的 @company/types 包中,遵守 semver,变更时通过 changesets 自动生成 changelog。 增量编译替代全量编译:使用 TypeScript 的 Project References 或 tsc --build 实现依赖感知的增量编译,大幅减少全量检查的场景。 类型可见性控制:包的 package.json 的 types 字段只暴露公共类型,内部类型通过 tsconfig.json 的 paths 映射避免被外部引用。 构建管道的缓存优化:结合 turborepo 的缓存策略,未变更包的编译产物直接复用缓存,CI 构建时间控制在 3 分钟以内。 方案设计 类型共享架构 在 monorepo 中,类型共享有三种模式:集中式类型包、分布式内联类型 和 接口契约生成。我们推荐集中式类型包作为默认方案。 集中式类型包 ( packages/types ) 放置所有跨包共享的类型定义,包括 API 请求/响应 DTO、配置接口、事件定义和常量枚举。其他包通过 @company/types 导入。这种模式的优点是类型定义单一来源,不存在多个副本的同步问题。缺点是可能演进为巨大的类型垃圾场,需要通过子域拆分和定期清理来控制: ...