Agent X-Ray
RuntimeNotesAbout
Notes/代码工程/TypeScript 深度教程/第9章

第9章:大型工程 —— Monorepo、项目引用、测试与性能

4 分钟 · 更新于 2026-09-01

第9章:大型工程 —— Monorepo、项目引用、测试与性能

当项目变大,类型安全必须与构建速度、包边界、发布契约和测试体系一起设计。本章建立大型 TypeScript 工程的反馈闭环。


一、为什么使用 Monorepo

Monorepo 将多个包放在同一仓库中:

text
workspace/
├── package.json
├── pnpm-workspace.yaml
├── tsconfig.base.json
└── packages/
    ├── domain/
    ├── api-client/
    └── web-app/

适用场景:

  • 多个应用共享领域模型和工具。
  • 需要原子提交跨包变更。
  • 希望统一 lint、测试和发布流程。

代价包括构建复杂度、依赖边界治理、缓存和版本策略,不应只为“目录整齐”采用。

二、共享基础配置

json
{
  "compilerOptions": {
    "target": "ES2022",
    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

各包继承并覆盖与自身环境相关的选项。浏览器包和 Node 包不应盲目共享全部 lib、模块和运行时类型。

三、Project References

包配置:

json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "composite": true,
    "rootDir": "src",
    "outDir": "dist"
  },
  "include": ["src"],
  "references": [
    { "path": "../domain" }
  ]
}

根配置:

json
{
  "files": [],
  "references": [
    { "path": "packages/domain" },
    { "path": "packages/api-client" },
    { "path": "packages/web-app" }
  ]
}

构建:

bash
npx tsc --build
npx tsc --build --clean

Project References 明确包依赖图,支持增量构建和声明边界,但要求每个 composite 项目正确声明输入输出。

四、包边界与公开 API

每个包应通过明确入口公开能力:

ts
// packages/domain/src/index.ts
export type { User, Order } from "./model.js";
export { parseUser } from "./validation.js";

避免消费端深入导入内部文件:

ts
// 不推荐
import { helper } from "@workspace/domain/src/internal/helper";

配合 package.jsonexports 限制入口,减少内部结构变更影响。

五、依赖规则

建议建立并自动检查:

  • 应用可以依赖领域和基础设施包。
  • 领域包不依赖 UI 和具体框架。
  • 包之间不形成循环依赖。
  • 类型包不成为任意共享杂物箱。
  • 同一第三方依赖的版本策略明确。

“共享类型”本身也是耦合。前后端共享数据库实体,可能把服务端内部字段泄露给客户端;更好的方式是共享稳定协议或 schema。

六、单元测试与类型检查

行为测试示例:

ts
import { describe, expect, it } from "vitest";
import { parsePort } from "./parse-port";

describe("parsePort", () => {
  it("接受合法端口", () => {
    expect(parsePort("8080")).toEqual({ ok: true, value: 8080 });
  });
});

类型测试关注编译契约:

ts
const id: string = getProperty(user, "id");

// @ts-expect-error 不存在的键必须失败
getProperty(user, "missing");

行为测试、类型测试和 tsc --noEmit 解决不同问题。

七、声明文件测试

库发布前至少验证:

  1. 生成 .d.ts
  2. 建立最小消费项目。
  3. 分别测试 ESM/CJS 或明确只支持其中一种。
  4. 验证 exportstypes 指向正确。
  5. 检查公共声明是否泄露无法解析的内部类型。

八、测试异步代码

测试应控制:

  • Promise 是否被等待。
  • 计时器是否使用 fake timers 或明确超时。
  • 网络是否通过接口注入或 mock server 隔离。
  • 失败路径和取消路径是否覆盖。
  • 测试结束后监听器、连接和定时器是否清理。

避免只测成功路径和快照。

九、编译性能

常见优化方向:

  • Project References 与 incremental
  • 缩小 include,不要把构建产物再次纳入。
  • 避免过度复杂的条件/递归类型。
  • 减少巨型联合和重复实例化。
  • 控制 barrel 文件造成的依赖扩散。
  • 使用 --extendedDiagnostics 观察数据。
  • 区分编辑器问题、类型检查问题和 bundler 问题。
bash
npx tsc --noEmit --extendedDiagnostics

不要在没有数据时通过关闭严格检查“优化性能”。

十、运行时性能与 TypeScript

类型通常被擦除,所以大部分类型本身不增加运行时成本。但以下设计会产生真实成本:

  • 枚举、装饰器和类字段可能输出代码。
  • 运行时 schema 校验需要 CPU,但提供边界安全。
  • 为满足类型而复制对象会增加内存。
  • 过度抽象可能影响 tree shaking 或包体积。

优化仍应基于运行时 profile 和 bundle 分析,而不是看到泛型就担心性能。

十一、CI 反馈链

一个基础流程:

text
安装锁定依赖
→ 格式与 lint
→ tsc --noEmit 或 tsc --build
→ 单元测试
→ 集成测试
→ 构建产物
→ 库消费测试

大型仓库再加入变更检测、远程缓存和按依赖图执行。

十二、版本升级

TypeScript 新版本可能带来:

  • 更严格或更准确的推断。
  • 声明文件兼容变化。
  • 模块解析变化。
  • 标准库类型更新。
  • 废弃配置。

升级步骤:

  1. 阅读 release notes。
  2. 在独立分支升级。
  3. 跑完整类型检查和测试。
  4. 区分真实缺陷、声明不兼容与推断变化。
  5. 不要用全局 skipany 快速压掉问题。

十三、常见误区

  1. Monorepo 包之间可以任意深层导入。
  2. Project References 配置存在但构建仍绕过依赖图。
  3. 前后端直接共享内部数据库实体。
  4. 只有运行时单测,没有类型契约测试。
  5. 发布库只测试源码,不测试打包后的消费体验。
  6. 通过关闭 strict 解决编译慢。
  7. 巨型高级类型没有性能预算。
  8. TypeScript 升级时用大量断言掩盖新增错误。

十四、实践练习

  1. 创建两个 composite 包并配置项目引用。
  2. 限制包公开入口,禁止深层导入。
  3. 为泛型工具增加 @ts-expect-error 类型测试。
  4. 使用 --extendedDiagnostics 记录一次检查基线。
  5. 建立最小消费项目验证生成的声明文件。
  6. 为异步 API 测试成功、失败、超时和取消。

十五、总结

  1. Monorepo 解决跨包协作,也引入边界和构建治理成本。
  2. Project References 将包依赖图交给 TypeScript 构建模式。
  3. 公共入口与 exports 应阻止消费端依赖内部结构。
  4. 行为测试、类型测试和声明消费测试缺一不可。
  5. 编译性能要通过诊断数据定位,不要牺牲类型规则逃避根因。
  6. TypeScript 升级属于工程变更,应通过完整反馈链验证。

请继续阅读:第10章:综合实战。


原始资料引用


  • 第8章:类的高级能力
  • 第10章:综合实战

本章目录
一、为什么使用 Monorepo二、共享基础配置三、Project References四、包边界与公开 API五、依赖规则六、单元测试与类型检查七、声明文件测试八、测试异步代码九、编译性能十、运行时性能与 TypeScript十一、CI 反馈链十二、版本升级十三、常见误区十四、实践练习十五、总结原始资料引用Related Documents
苏ICP备2025204887号-2