在 TypeScript 中,references
(项目引用)是用于管理多项目依赖关系 的核心配置,通过 tsconfig.json
中的 references
字段实现。其核心作用是将大型项目拆分为多个独立子项目(如工具库、业务模块、UI 组件库等),支持子项目间的隔离编译、依赖复用,同时提升整体编译效率(仅重新编译修改的子项目)。
使用示例:
json
{
"references": [
{ "path": "../utils/tsconfig.json" }
]
}
一、使用前提
要使用 references
,被引用的子项目(依赖项)必须满足以下约束:
- 子项目的
tsconfig.json
中需设置"composite": true
(启用复合项目模式,允许被其他项目引用); - 子项目需明确指定
"rootDir"
(源代码根目录)和"outDir"
(编译输出目录),确保输出文件结构可被引用方识别; - 建议开启
"declaration": true
(生成.d.ts
类型声明文件),确保引用方能获取类型提示。
二、核心配置字段
1. 引用方配置(references
字段)
在需要依赖其他子项目的「引用方项目」的 tsconfig.json
中,通过 references
数组声明依赖,每个依赖项包含以下属性:
path
(必填):指向被引用子项目的tsconfig.json
文件路径(相对路径或绝对路径);prepend
(可选,默认true
):若为true
,被引用项目的编译输出会自动添加到引用方的模块查找路径中,无需手动配置baseUrl
或paths
。
2. 被引用方配置(复合项目约束)
被引用的「子项目」的 tsconfig.json
需包含以下关键配置:
json
json
{
"compilerOptions": {
"composite": true, // 启用复合项目,允许被引用
"rootDir": "./src", // 源代码根目录(必须明确)
"outDir": "./dist", // 编译输出目录(必须明确)
"declaration": true, // 生成 .d.ts 类型声明(推荐)
"declarationMap": true // 生成类型声明的源映射(可选,便于调试)
},
"include": ["./src/**/*"] // 明确包含的源代码文件
}
三、使用步骤与示例
假设我们有一个大型项目,拆分为 3 个子项目,结构如下:
plaintext
scss
my-monorepo/ // 根目录(可包含根 tsconfig.json,也可仅作为文件夹)
├─ packages/
│ ├─ utils/ // 子项目 1:工具库(被依赖项)
│ │ ├─ src/ // utils 源代码
│ │ │ └─ math.ts // 工具函数(如 add 方法)
│ │ └─ tsconfig.json // utils 的配置(复合项目)
│ ├─ components/ // 子项目 2:UI 组件库(依赖 utils)
│ │ ├─ src/ // components 源代码
│ │ │ └─ Button.tsx // UI 组件(使用 utils 的 add 方法)
│ │ └─ tsconfig.json // components 的配置(引用 utils)
│ └─ app/ // 子项目 3:主应用(依赖 utils 和 components)
│ ├─ src/ // app 源代码
│ │ └─ index.tsx // 主应用入口(使用 components 的 Button)
│ └─ tsconfig.json // app 的配置(引用 utils 和 components)
└─ package.json // 根项目依赖(可选,用于管理 monorepo 依赖)
步骤 1:配置被引用方(utils 子项目)
packages/utils/tsconfig.json
(复合项目配置):
json
json
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"composite": true, // 启用复合项目
"rootDir": "./src", // 源代码根目录
"outDir": "./dist", // 编译输出到 dist 目录
"declaration": true, // 生成 .d.ts 类型声明
"strict": true
},
"include": ["./src/**/*"], // 包含所有 src 下的文件
"exclude": ["node_modules"]
}
在 packages/utils/src/math.ts
中编写工具函数:
typescript
typescript
// 工具函数:加法
export const add = (a: number, b: number): number => a + b;
步骤 2:配置引用方 1(components 子项目,依赖 utils)
packages/components/tsconfig.json
(声明对 utils 的引用):
json
json
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"jsx": "react-jsx", // 假设是 React 组件库
"rootDir": "./src",
"outDir": "./dist",
"declaration": true,
"strict": true
},
"include": ["./src/**/*"],
"exclude": ["node_modules"],
// 声明依赖:引用 utils 子项目
"references": [
{ "path": "../utils/tsconfig.json" } // 指向 utils 的 tsconfig 路径
]
}
在 packages/components/src/Button.tsx
中使用 utils 的 add
函数:
tsx
typescript
// 导入 utils 的 add 函数(无需配置 paths,因 references.prepend 默认 true)
import { add } from "@my-monorepo/utils"; // 假设 utils 在 package.json 中声明为 "@my-monorepo/utils"
// 或相对路径:import { add } from "../utils/dist/src/math";(不推荐,references 会自动处理路径)
interface ButtonProps {
count: number;
}
export const Button = ({ count }: ButtonProps) => {
// 使用 utils 的 add 函数
const handleClick = () => alert(`新计数:${add(count, 1)}`);
return <button onClick={handleClick}>点击加 1</button>;
};
步骤 3:配置引用方 2(app 主应用,依赖 utils 和 components)
packages/app/tsconfig.json
(声明对 utils 和 components 的引用):
json
json
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"jsx": "react-jsx",
"rootDir": "./src",
"outDir": "./dist",
"strict": true
},
"include": ["./src/**/*"],
"exclude": ["node_modules"],
// 声明多个依赖:utils 和 components
"references": [
{ "path": "../utils/tsconfig.json" },
{ "path": "../components/tsconfig.json" }
]
}
在 packages/app/src/index.tsx
中使用 components 的 Button
组件:
tsx
javascript
import { Button } from "@my-monorepo/components"; // 导入 UI 组件
import React from "react";
import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
{/* 使用 components 的 Button 组件,传递 count 属性 */}
<Button count={0} />
</React.StrictMode>
);
步骤 4:编译与运行
-
单独编译子项目 :进入某个子项目目录,执行
tsc
即可编译(如编译 utils:cd packages/utils && tsc
); -
编译所有依赖项目 :在引用方项目中执行
tsc --build
(缩写tsc -b
),TypeScript 会自动编译所有被引用的子项目(若子项目已编译且无修改,则跳过);- 示例:在 app 目录执行
tsc -b
,会先检查 utils 和 components 是否最新,若未编译则自动编译,再编译 app;
- 示例:在 app 目录执行
-
运行主应用 :编译完成后,执行 app 的输出文件(如
node packages/app/dist/src/index.js
,或根据框架配置启动)。
四、常见场景与注意事项
1. 场景扩展:根项目统一管理
若需要统一编译所有子项目,可在根目录创建 tsconfig.json
,通过 references
包含所有子项目,实现「一键编译全部」:
json
json
// 根目录 tsconfig.json
{
"files": [], // 根项目无源代码,仅用于管理引用
"references": [
{ "path": "./packages/utils" },
{ "path": "./packages/components" },
{ "path": "./packages/app" }
]
}
执行 tsc -b
即可从根目录编译所有子项目。
2. 注意事项
-
避免循环引用:不可出现 A 引用 B、B 引用 A 的循环依赖,会导致编译错误;
-
路径正确性 :
references.path
必须指向被引用项目的tsconfig.json
文件(而非文件夹,若指向文件夹,TypeScript 会自动查找其中的tsconfig.json
); -
composite
必开启 :被引用项目若未设置"composite": true
,引用方会报错; -
类型声明依赖 :若被引用项目未开启
"declaration": true
,引用方可能无法获取类型提示(仅能运行,但失去 TypeScript 类型检查能力)。
通过 references
,可将大型项目拆分为高内聚、低耦合的子模块,不仅提升编译效率,还便于团队协作(不同团队维护不同子项目),是 TypeScript 大型应用的最佳实践之一。