tsconfig.json 核心结构
tsconfig.json 是 TypeScript 项目的核心配置文件,控制编译行为、类型检查严格程度和文件包含范围。理解每个选项的含义,能让你精确控制 TypeScript 的行为,在严格性和开发效率之间找到最佳平衡点。
compilerOptions
编译器选项,控制 TypeScript 编译器(tsc)的具体行为,包括目标语法版本、模块格式、类型检查严格程度、路径别名等。这是最重要的部分。
include / exclude
控制哪些文件被编译。include 是白名单(glob 模式),exclude 是黑名单。默认 include 所有 .ts/.tsx/.d.ts 文件(不含 node_modules),通常需要显式 exclude node_modules 和 dist。
extends
继承另一个 tsconfig.json 的配置(如 tsconfig/bases 提供的预设)。子配置可以覆盖或追加父配置的选项,常用于 monorepo 或共享基础配置。
references
项目引用,用于 monorepo 多包场景,支持增量编译和类型隔离。被引用的包需要设置 "composite": true 并生成 .d.ts。
{
"compilerOptions": {
// ── 输入/输出 ──
"rootDir": "./src", // 源码根目录
"outDir": "./dist", // 编译输出目录
"rootDirs": ["./src", "./generated"], // 多根目录
// ── 编译目标 ──
"target": "ES2022", // 输出 JS 的语法版本
"module": "ESNext", // 模块格式
"moduleResolution": "bundler", // 模块解析策略
"lib": ["ES2022", "DOM"], // 包含的类型库
// ── 严格性 ──
"strict": true, // 开启所有严格检查
// ── 输出选项 ──
"declaration": true, // 生成 .d.ts 声明文件
"declarationMap": true, // 生成 .d.ts.map 源码映射
"sourceMap": true, // 生成 .js.map
"removeComments": false,
// ── 路径 ──
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
target 与 lib 的关系
target 决定编译后 JavaScript 的语法版本(箭头函数、async/await 是否保留或降级)。lib 决定 TypeScript 知道哪些内置 API 类型(如 Array.prototype.at 是 ES2022 特性,必须将 "ES2022" 加入 lib)。这是两个独立的概念——target 影响代码,lib 影响类型检查。
// 常见场景配置示例
// 场景1:浏览器 SPA(使用打包器)
{
"target": "ES2020", // 浏览器支持 ES2020 语法
"module": "ESNext", // 保留 import/export 给打包器处理
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
// 场景2:Node.js 后端 CJS
{
"target": "ES2022", // Node.js 18+ 支持
"module": "CommonJS", // 编译为 require/module.exports
"moduleResolution": "node",
"lib": ["ES2022"] // 不需要 DOM
}
// 场景3:Node.js 后端 ESM
{
"target": "ES2022",
"module": "NodeNext", // Node.js 原生 ESM
"moduleResolution": "NodeNext", // 必须配套
"lib": ["ES2022"]
}
strict 模式详解
strict: true 等效于哪些选项?
strictNullChecks
null 和 undefined 不再是所有类型的子类型,必须显式处理可能为 null/undefined 的值。这是最重要的严格选项,能捕获大量 NPE 错误。
noImplicitAny
禁止隐式 any 类型。如果 TypeScript 无法推断类型且你没有标注,会报错。强制你显式声明所有类型或 unknown。
strictFunctionTypes
函数参数类型采用逆变(Contravariant)检查,防止不安全的函数类型替换。
strictBindCallApply
对 .bind()、.call()、.apply() 进行严格类型检查。
strictPropertyInitialization
类的属性必须在构造函数中初始化,或声明为可选/有默认值。防止「意外的 undefined 属性」。
noImplicitThis
禁止 this 隐式为 any 类型。需要明确 this 的类型(通过 this 参数标注)。
useUnknownInCatchVariables
catch 块的 error 参数类型为 unknown(而非 any),强制进行类型检查后才能使用。
额外推荐的严格选项
以下选项不在 strict: true 的范围内,但强烈推荐在新项目中开启:
{
"compilerOptions": {
"strict": true,
// noUncheckedIndexedAccess:数组和对象索引访问的类型包含 undefined
// arr[0] 类型变为 T | undefined,强制你检查元素是否存在
// 防止越界访问导致的运行时错误
"noUncheckedIndexedAccess": true,
// noImplicitOverride:在子类中重写父类方法必须显式加 override 关键字
// 防止意外重写(拼写错误导致新建了方法而非覆盖)
"noImplicitOverride": true,
// exactOptionalPropertyTypes:严格区分"属性值为 undefined"和"属性不存在"
// { a?: string } 表示属性可以不存在,但存在时必须是 string(不能是 undefined)
// 需要同时加 | undefined 才能允许赋值 undefined
"exactOptionalPropertyTypes": true,
// verbatimModuleSyntax:强制区分类型导入和值导入
// 仅导入类型的 import 必须写成 import type
// 防止意外引入副作用,也让打包器更好地 Tree Shake
"verbatimModuleSyntax": true,
// 报告未使用的局部变量(可在测试中用 _ 前缀绕过)
"noUnusedLocals": true,
// 报告未使用的函数参数(_param 前缀的参数会被忽略)
"noUnusedParameters": true,
// 强制所有代码路径都有返回值(对于有返回类型的函数)
"noImplicitReturns": true,
// 防止 switch-case 意外穿透(fall-through)
"noFallthroughCasesInSwitch": true
}
}
noUncheckedIndexedAccess 的实际影响
// noUncheckedIndexedAccess: true 时
const arr: string[] = ['a', 'b'];
const first = arr[0]; // 类型:string | undefined
// 必须做非空检查才能使用
if (first !== undefined) {
console.log(first.toUpperCase()); // OK
}
// 或用可选链
console.log(first?.toUpperCase());
// 对象索引访问同样受影响
const map: Record<string, number> = {};
const val = map['key']; // 类型:number | undefined
项目引用(Project References)
// tsconfig.json(根)
{
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" },
{ "path": "./apps/web" }
]
}
// packages/core/tsconfig.json
{
"compilerOptions": {
"composite": true, // 必须:启用项目引用
"declaration": true, // 必须:生成 .d.ts
"outDir": "./dist"
}
}
# 构建所有项目(增量编译)
npx tsc --build
npx tsc -b
# 只做类型检查(CI 使用)
npx tsc --noEmit
ts-node vs tsx vs SWC:选哪个?
ts-node:完整类型检查,慢,适合脚本和调试。tsx:跳过类型检查的极速执行,适合开发时热重载(tsx watch src/index.ts)。SWC/esbuild:Rust/Go 实现的超快转译器,适合构建流水线。推荐:开发时用 tsx,CI 用 tsc --noEmit 检查类型,构建用 esbuild/SWC。
常见 tsconfig 继承模式
// tsconfig.base.json(根目录,共享基础配置)
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"verbatimModuleSyntax": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
// tsconfig.json(应用,继承基础配置并覆盖)
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"lib": ["ES2022", "DOM"], // 添加 DOM 类型
"outDir": "./dist",
"sourceMap": true,
"declaration": false // 应用不需要生成 .d.ts
},
"include": ["src/**/*"]
}
// tsconfig.test.json(测试环境单独配置)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noUnusedLocals": false, // 测试文件允许未使用变量
"types": ["vitest/globals"] // 引入测试框架全局类型
},
"include": ["src/**/*", "tests/**/*"]
}
本章小结
本章核心要点
- target vs lib:target 控制编译后 JS 的语法版本(影响代码),lib 控制 TS 知道哪些内置 API 类型(影响类型检查);两者独立,现代项目 target 通常设 ES2020 或更高,lib 需要手动添加所需的库。
- strict: true 包含 7 项子选项:最重要的是 strictNullChecks(null/undefined 安全)和 noImplicitAny(禁止隐式 any);对新项目始终开启 strict,对旧项目迁移时逐步启用。
- 额外推荐选项:noUncheckedIndexedAccess(数组索引安全)、verbatimModuleSyntax(强制 import type)、noImplicitOverride(安全类继承)在新项目中强烈推荐开启。
- module + moduleResolution 必须配套:bundler 项目用 module: ESNext + moduleResolution: bundler;Node.js ESM 用 module: NodeNext + moduleResolution: NodeNext;两者不一致会导致奇怪的类型错误。
- 项目引用(composite):monorepo 中多包之间的依赖关系用 references + composite 声明,支持增量编译(tsc -b),避免跨包的全量重编译。