Chapter 08

tsconfig.json 详解与编译器选项

全面掌握 tsconfig.json 核心配置,理解严格模式各选项与项目引用

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/**/*"]
}

本章小结

本章核心要点