跳到主要内容

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-* 可以依赖 coreui
  • ui 可以依赖 core
  • core 不依赖任何 app-*

2) 使用 Project References(项目引用)做增量构建与隔离

“references”这里指的是 TypeScript Project Referencestsconfig.jsonreferences),它是构建大型 TS 工程的关键特性之一,和三斜线引用不是一回事。

每个 package 一个 tsconfig.json,开启:

  • composite: true(必须)
  • declaration: true(推荐)
  • declarationMap: true(可选)
  • outDir / rootDir(明确)
  • tsBuildInfoFile(可选,放在 node_modules/.cachedist

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),并在 tsconfiginclude 中显式包含
  • 避免到处使用 /// <reference ...> 拼可见性

4) 明确每个 package 的运行时与类型环境(DOM vs Node)

常见坑:在 Node 包里不小心引入 DOM 的类型,或在 Web 包里引入 Node 的全局类型,导致类型冲突。

建议做法:

  • app-webtsconfiglib 包含 dom
  • app-nodetsconfiglib 不要 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 或类似规则,禁止“反向依赖”
  • tsconfigpaths 仅用于开发体验(需要配合 bundler/运行时别名一致),不要滥用造成“编译能过运行挂”