我的博客
返回首页

02-TS历史与演进

分类:2-技术文章/02-TypeScript全栈开发/TS基础与类型系统/04-TypeScript类型系统
发布于:
阅读时间:18 分钟

这段文字其实是在描述 TypeScript 项目引用(Project References) 出现之前的痛点,以及它如何完美解决这些痛点。

简单来说,这段话的核心逻辑是:“以前我们用一个

tsconfig.json
管所有代码(源码 + 测试),结果发现有很多不合理的地方;现在有了‘项目引用’,我们可以把源码和测试拆成两个独立的项目,既快又安全。”

为了让你彻底明白,我把这段话拆解成三个部分:旧模式的痛点为什么多配置文件也不够好、以及项目引用是如何解决的


1. 旧模式:单一
tsconfig.json
的痛点

在以前,大家通常只写一个

tsconfig.json
,把
src
(源码)和
test
(测试)都放进去编译。

// 旧的 tsconfig.json
{
  "include": ["src/**/*", "test/**/*"]
}

这就导致了文中提到的四个“笨拙”的问题:

  1. 实现文件也可以导入测试文件(循环依赖风险)

    • 问题:因为都在一个项目里,TS 允许你在
      src/converter.ts
      里写
      import { ... } from "../test/converter-tests"
    • 后果:这是严重的架构错误!源码不应该依赖测试代码。但在单配置下,TS 不会阻止你这么做,直到运行时报错。
  2. 输出目录混乱(无法隔离构建)

    • 问题:如果你设置
      outDir: "./dist"
      ,TS 会把
      src
      test
      的代码都编译到
      dist
      里。
    • 后果:你的生产环境包里混入了测试代码。如果你想把测试代码编译到另一个文件夹(比如
      dist-test
      ),单配置很难优雅地做到(需要复杂的
      exclude
      或多个输出配置,容易出错)。
  3. 改源码,必须重测测试(浪费时间的类型检查)

    • 场景:你修改了
      src/converter.ts
      的内部逻辑(但接口没变)。
    • 后果:TS 会重新检查整个项目,包括
      test/converter-tests.ts
    • 吐槽:测试文件的类型依赖于源码接口。如果接口没变,测试文件的类型检查其实是完全没必要的,但 TS 还是跑了,浪费时间。
  4. 改测试,必须重测源码(无意义的等待)

    • 场景:你只是修改了
      test/converter-tests.ts
      里的某个断言写法。
    • 后果:TS 会重新检查整个项目,包括
      src/converter.ts
    • 吐槽:测试文件的改动根本不影响源码的类型,这波检查纯属浪费。

2. 进阶尝试:用多个
tsconfig
手动拆分

为了解决上面的问题,你可能会想:“那我搞两个配置文件不就行了?”

  • tsconfig.src.json
    (只管 src)
  • tsconfig.test.json
    (只管 test)

但这又带来了新的麻烦(文中提到的“新问题”):

  1. 缺少内置的实时检查
    • 你需要手动运行两次命令:
      tsc -p tsconfig.src.json
      tsc -p tsconfig.test.json
      。漏跑一个就不知道有没有错。
  2. 等待时间加倍
    • 即使你没改代码,每次全量构建都要跑两遍编译器,启动两次进程,总耗时 = A + B。
  3. 监听模式(-w)失效
    • tsc -w
      一次只能监听一个配置文件。你想同时监听源码和测试?那你得开两个终端窗口,或者写个复杂的 shell 脚本并行运行,体验很差。
  4. 依赖关系难以管理
    • 如果测试依赖源码,手动配置两个文件时,很难告诉 TS:“先编译完 src,生成了
      .d.ts
      之后,再去编译 test”。

3. 终极方案:工程引用 (Project References)

“工程引用可以解决全部这些问题” 的意思就是:TypeScript 官方推出了一种机制,让你既能拆分项目(解决痛点),又能统一管理(解决新问题)。

它是如何工作的?

你会创建这样的结构:

根目录

tsconfig.json
(作为解决方案文件)

{
  "files": [],
  "references": [
    { "path": "./src" },      // 引用 src 项目
    { "path": "./test" }      // 引用 test 项目
  ]
}

/src/tsconfig.json
(独立项目)

{
  "compilerOptions": {
    "composite": true,        // 关键:标记为可被引用的复合项目
    "outDir": "../dist/src",
    "rootDir": "."
  },
  "include": ["./**/*"]
}

/test/tsconfig.json
(独立项目)

{
  "compilerOptions": {
    "composite": true,
    "outDir": "../dist/test",
    "rootDir": "."
  },
  "include": ["./**/*"],
  "references": [
    { "path": "../src" }      // 关键:明确声明 test 依赖 src
  ]
}

它如何解决那 4 个痛点?

  1. 禁止源码导入测试
    • 因为
      src
      项目里没有引用
      test
      项目。如果你在
      src
      里 import
      test
      的文件,TS 会直接报错:“找不到模块”,因为在这个项目的视野里,test 根本不存在。
  2. 输出目录隔离
    • src
      输出到
      dist/src
      test
      输出到
      dist/test
      。互不干扰,发布时只发
      dist/src
      即可。
  3. 智能增量编译(解决重复检查)
    • 当你修改
      src
      时:TS 只会重新编译
      src
    • 关键点
      test
      项目依赖的是
      src
      生成的
      .d.ts
      (声明文件)
      。只要
      src
      对外接口没变,
      .d.ts
      就不变,
      test
      项目甚至不需要重新进行类型检查!
      这极大地节省了时间。
  4. 统一构建与监听
    • 你只需要在根目录运行一次:
      tsc -b
      (build) 或
      tsc -b -w
      (watch)。
    • TS 会自动分析依赖图:先编
      src
      ,等它生成
      .d.ts
      后,自动接着编
      test
    • 只用一个命令,一个终端窗口,就能享受增量编译和实时检查。

总结

那段话的意思是:

“以前我们要么忍受单配置的混乱和低效,要么忍受多配置的繁琐和割裂。工程引用(Project References) 让我们能够像搭积木一样,把大项目拆成有依赖关系的小项目(如 src 依赖 test),既保证了架构的纯净(防止循环依赖),又利用

.d.ts
实现了极致的增量编译加速(改哪里只查哪里)。”

这就是为什么在 Monorepo 和大型工程中,Project References 是必备的神器。

composite如何理解