TypeScript Project References
正确构建 TypeScript 大型项目:一套实用结构与配置思路
下面是一套更接近“可维护、可扩展、可拆分”的 TS 大型工程实践,核心目标是:清晰的依赖方向、稳定的类型边界、可增量构建、可发布与可测试。
1) 先定边界:Monorepo + 多 package(推荐)或单 repo 分层
当代码规模增长时,最常见的痛点是“所有代码都在一个 tsconfig 下互相引用”。更稳的做法是把系统拆成多个 package(或至少多个逻辑层),并规定依赖只能单向流动。
一个典型 monorepo 结构:
repo/
packages/
core/ # 纯业务无关能力:类型、工具、domain
app-web/ # Web 应用
app-node/ # Node 服务/脚本
ui/ # UI 组件库
tsconfig.base.json
package.json
依赖方向示例:
app-*可以依赖core、uiui可以依赖corecore不依赖任何app-*
2) 使用 Project References(项目引用)做增量构建与隔离
“references”这里指的是 TypeScript Project References(tsconfig.json 的 references),它是构建大型 TS 工程的关键特性之一,和三斜线引用不是一回事。
每个 package 一个 tsconfig.json,开启:
composite: true(必须)declaration: true(推荐)declarationMap: true(可选)outDir/rootDir(明确)tsBuildInfoFile(可选,放在node_modules/.cache或dist)
packages/core/tsconfig.json 示例:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
packages/app-web/tsconfig.json 示例(引用 core/ui):
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "dist",
"rootDir": "src"
},
"references": [
{ "path": "../core" },
{ "path": "../ui" }
],
"include": ["src"]
}
根目录 tsconfig.json(只做聚合构建):
{
"files": [],
"references": [
{ "path": "packages/core" },
{ "path": "packages/ui" },
{ "path": "packages/app-web" }
]
}
构建时用:
tsc -b(build 模式,会按引用顺序增量编译)tsc -b -w(watch)
优势:
- 增量编译快
- package 间通过
.d.ts形成更清晰的 类型边界 - 依赖违规更容易暴露(尤其配合 lint / 约束)
3) 以“模块”为中心,而不是靠全局类型“到处可见”
大型项目里最容易失控的是全局污染。建议:
- 尽量用
export type/export interface输出类型 - 需要全局扩展时,集中到少数文件(如
types/global.d.ts),并在tsconfig的include中显式包含 - 避免到处使用
/// <reference ...>拼可见性
4) 明确每个 package 的运行时与类型环境(DOM vs Node)
常见坑:在 Node 包里不小心引入 DOM 的类型,或在 Web 包里引入 Node 的全局类型,导致类型冲突。
建议做法:
app-web的tsconfig:lib包含domapp-node的tsconfig:lib不要dom,需要 Node 类型就用types: ["node"](在 tsconfig 里统一)
5) 选择合适的模块与解析策略(现代默认)
在新项目中,优先考虑:
moduleResolution:Bundler(配合 Vite/ESBuild 等)或NodeNext(严格对齐 Node ESM)module: 取决于运行时与打包方式(ESNext常见)verbatimModuleSyntax:true(更接近真实运行时 import/export 行为)skipLibCheck: 大型项目常设为true以提升速度(权衡)
6) 类型检查与构建分离(可选但很实用)
很多团队会:
- 用 bundler(Vite/Rspack/Webpack)做实际打包
- 用
tsc -b只做类型检查与产物声明(.d.ts)
这样可以兼顾速度与类型安全。
7) 约束依赖与导入路径
建议组合:
eslint+import/no-restricted-paths或类似规则,禁止“反向依赖”tsconfig的paths仅用于开发体验(需要配合 bundler/运行时别名一致),不要滥用造成“编译能过运行挂”