02-TS历史与演进
分类:2-技术文章/02-TypeScript全栈开发/TS基础与类型系统/04-TypeScript类型系统
发布于:
阅读时间:18 分钟
这段文字其实是在描述 TypeScript 项目引用(Project References) 出现之前的痛点,以及它如何完美解决这些痛点。
简单来说,这段话的核心逻辑是:“以前我们用一个 管所有代码(源码 + 测试),结果发现有很多不合理的地方;现在有了‘项目引用’,我们可以把源码和测试拆成两个独立的项目,既快又安全。”tsconfig.json
为了让你彻底明白,我把这段话拆解成三个部分:旧模式的痛点、为什么多配置文件也不够好、以及项目引用是如何解决的。
1. 旧模式:单一 tsconfig.json
的痛点
tsconfig.json在以前,大家通常只写一个
tsconfig.jsonsrctest// 旧的 tsconfig.json
{
"include": ["src/**/*", "test/**/*"]
}
这就导致了文中提到的四个“笨拙”的问题:
-
实现文件也可以导入测试文件(循环依赖风险)
- 问题:因为都在一个项目里,TS 允许你在 里写
src/converter.ts。import { ... } from "../test/converter-tests" - 后果:这是严重的架构错误!源码不应该依赖测试代码。但在单配置下,TS 不会阻止你这么做,直到运行时报错。
- 问题:因为都在一个项目里,TS 允许你在
-
输出目录混乱(无法隔离构建)
- 问题:如果你设置 ,TS 会把
outDir: "./dist"和src的代码都编译到test里。dist - 后果:你的生产环境包里混入了测试代码。如果你想把测试代码编译到另一个文件夹(比如 ),单配置很难优雅地做到(需要复杂的
dist-test或多个输出配置,容易出错)。exclude
- 问题:如果你设置
-
改源码,必须重测测试(浪费时间的类型检查)
- 场景:你修改了 的内部逻辑(但接口没变)。
src/converter.ts - 后果:TS 会重新检查整个项目,包括 。
test/converter-tests.ts - 吐槽:测试文件的类型依赖于源码接口。如果接口没变,测试文件的类型检查其实是完全没必要的,但 TS 还是跑了,浪费时间。
- 场景:你修改了
-
改测试,必须重测源码(无意义的等待)
- 场景:你只是修改了 里的某个断言写法。
test/converter-tests.ts - 后果:TS 会重新检查整个项目,包括 。
src/converter.ts - 吐槽:测试文件的改动根本不影响源码的类型,这波检查纯属浪费。
- 场景:你只是修改了
2. 进阶尝试:用多个 tsconfig
手动拆分
tsconfig为了解决上面的问题,你可能会想:“那我搞两个配置文件不就行了?”
- (只管 src)
tsconfig.src.json - (只管 test)
tsconfig.test.json
但这又带来了新的麻烦(文中提到的“新问题”):
- 缺少内置的实时检查:
- 你需要手动运行两次命令:和
tsc -p tsconfig.src.json。漏跑一个就不知道有没有错。tsc -p tsconfig.test.json
- 你需要手动运行两次命令:
- 等待时间加倍:
- 即使你没改代码,每次全量构建都要跑两遍编译器,启动两次进程,总耗时 = A + B。
- 监听模式(-w)失效:
- 一次只能监听一个配置文件。你想同时监听源码和测试?那你得开两个终端窗口,或者写个复杂的 shell 脚本并行运行,体验很差。
tsc -w
- 依赖关系难以管理:
- 如果测试依赖源码,手动配置两个文件时,很难告诉 TS:“先编译完 src,生成了 之后,再去编译 test”。
.d.ts
- 如果测试依赖源码,手动配置两个文件时,很难告诉 TS:“先编译完 src,生成了
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 个痛点?
- 禁止源码导入测试 ✅
- 因为 项目里没有引用
src项目。如果你在test里 importsrc的文件,TS 会直接报错:“找不到模块”,因为在这个项目的视野里,test 根本不存在。test
- 因为
- 输出目录隔离 ✅
- 输出到
src,dist/src输出到test。互不干扰,发布时只发dist/test即可。dist/src
- 智能增量编译(解决重复检查) ✅
- 当你修改 时:TS 只会重新编译
src。src - 关键点:项目依赖的是
test生成的src(声明文件)。只要.d.ts的对外接口没变,src就不变,.d.ts项目甚至不需要重新进行类型检查! 这极大地节省了时间。test
- 当你修改
- 统一构建与监听 ✅
- 你只需要在根目录运行一次:(build) 或
tsc -b(watch)。tsc -b -w - TS 会自动分析依赖图:先编 ,等它生成
src后,自动接着编.d.ts。test - 只用一个命令,一个终端窗口,就能享受增量编译和实时检查。
- 你只需要在根目录运行一次:
总结
那段话的意思是:
“以前我们要么忍受单配置的混乱和低效,要么忍受多配置的繁琐和割裂。工程引用(Project References) 让我们能够像搭积木一样,把大项目拆成有依赖关系的小项目(如 src 依赖 test),既保证了架构的纯净(防止循环依赖),又利用
实现了极致的增量编译加速(改哪里只查哪里)。”.d.ts
这就是为什么在 Monorepo 和大型工程中,Project References 是必备的神器。