TypeScript 项目的编译器配置文件,JSON 格式(支持注释,实际是 JSONC)。定义类型检查规则、模块解析策略、编译目标和输出行为,tsc 和绝大多数构建工具(Vite/esbuild/webpack 的 TS 插件、ts-node 等)都会读取它来决定如何处理 .ts 文件。

核心字段

字段说明
compilerOptions编译器行为配置,绝大部分字段都在这里
include需要编译的文件/目录 glob 列表
exclude排除的文件/目录,默认排除 node_modules
extends继承另一个 tsconfig 文件,支持继承 npm 包(如 @tsconfig/node20)
referencesProject References,声明依赖的其他 TS 子项目,用于 monorepo 增量构建

compilerOptions 常用项

字段说明
target编译产物的 JS 版本(ES2020/ESNext 等),决定语法降级程度
module输出的模块系统(CommonJS/ESNext/NodeNext 等)
moduleResolution模块解析算法,现代项目通常用 Bundler 或 NodeNext
lib引入的内置类型声明库(如 DOM、ES2022)
strict开启全部严格类型检查(等价于同时打开 strictNullChecks/noImplicitAny 等一组开关),新项目推荐默认开启
outDir / rootDir编译输出目录 / 源码根目录
noEmit只做类型检查不产出文件,常见于用 Vite/esbuild 转译、tsc 仅做类型校验的场景
esModuleInterop允许 import foo from 'commonjs-pkg' 风格导入 CJS 包
skipLibCheck跳过 .d.ts 声明文件的类型检查,提速,几乎所有项目都会开
paths + baseUrl路径别名映射,如 "@/*": ["src/*"],需要构建工具单独支持才能在运行时生效
isolatedModules要求每个文件可独立转译(不能依赖跨文件类型信息),用 esbuild/swc 等单文件转译器时必须开启
declaration生成 .d.ts 类型声明文件,库项目发布时常用

target / module / moduleResolution / lib 详解

这四个字段是最容易混淆的一组,因为它们回答的是四个不同的问题:

字段回答的问题
target我能用多新的 JS 语法?编译器要帮我转译掉多少?
module产物用什么模块语法(require 还是 import)?
moduleResolutionTS 按谁的规则找 import 对应的文件(Node 本身,还是打包工具)?
lib我能用哪些全局 API(浏览器 DOM,还是纯 JS 内置对象)?

target:数字越新(ES2020 → ES2022 → ESNext),编译器越少做语法降级,产物越接近原始 TS 代码。ESNext 永远跟最新草案走,不做任何转换,风险是运行环境可能还不支持某些新语法。选择依据是代码实际运行环境(Node 版本/浏览器兼容范围),不是越新越好。

module:CommonJS 产物用 require()/module.exports,老 Node 项目和大部分 npm 包仍是这个;ESNext 保留 import/export 原样,交给下游打包工具处理;NodeNext 不是固定选一种,而是让 TS 模拟 Node.js 自己的判断逻辑——根据文件是 .mts/.cts/.ts 以及 package.json 的 "type" 字段动态决定该文件按 ESM 还是 CJS 语义处理,是直接跑在 Node 上的项目目前最推荐的选项。

moduleResolution:只影响类型检查阶段”怎么找文件”,不影响运行时真正找文件的逻辑(那是 Node 或打包工具自己的事,TS 只是要在检查时对齐它)。Bundler 贴近 Vite/webpack/esbuild 这类工具较宽松的解析规则(可省略扩展名、支持 alias),避免 TS 报错但打包工具其实能跑;NodeNext 贴近 Node.js 真实的 ESM 解析算法(严格要求扩展名、遵守 package.json 的 exports 字段),需配合 module: NodeNext 一起用。一句话:项目最终由谁”实际加载文件”,moduleResolution 就该配套选谁的规则。

lib:声明项目里能用哪些内置 API 的类型。除了 ES2022 这类语言内置对象类型(Promise、Array 方法等),加上 DOM 才有 window/document/fetch 等浏览器全局对象的类型。纯后端 Node 项目通常不加 DOM(避免误用浏览器 API 却在服务端才崩溃);前端项目基本都要加。

两种最常见的组合可作为记忆锚点:

场景targetmodulemoduleResolutionlib
纯 Node 后端ES2022NodeNextNodeNext["ES2022"](不含 DOM)
前端/打包工具项目ESNext(或 ES2020)ESNextBundler["ES2022", "DOM"]

module 与 moduleResolution 几乎总是配对出现(Node 系配 NodeNext+NodeNext,打包工具系配 ESNext+Bundler);target 和 lib 则各自独立,分别取决于「运行环境新旧」和「是否需要浏览器 API」。

典型配置示例

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022", "DOM"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "isolatedModules": true,
    "noEmit": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

noEmit: true + isolatedModules: true 是「TS 只管类型检查、转译交给 Vite/esbuild」这一常见组合的标志性配置:tsc --noEmit 单独跑类型校验(CI 里常见),实际打包由 Vite 等工具用更快的单文件转译器完成,不经过 TS 编译器输出产物。

与构建工具的关系

  • tsc:官方编译器,既能类型检查也能输出 JS。项目里若只想做类型检查(配合其他工具转译),用 tsc --noEmit
  • Vite:默认不做类型检查,只用 esbuild 快速转译(丢弃类型信息),仍会读取 tsconfig.json 里的 paths/target 等字段影响转译行为;类型检查通常靠单独跑 tsc --noEmit 或装 vite-plugin-checker
  • ts-node:直接读取 tsconfig.json 在 Node 运行时动态转译执行 .ts 文件,无需预编译
  • Monorepo:用 references + composite: true 声明子项目依赖关系,tsc -b 按依赖顺序增量构建,避免每次全量类型检查

extends 继承模式

// tsconfig.json(应用层,继承基础配置再覆盖)
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist"
  }
}

Monorepo 常见做法:根目录放一份 tsconfig.base.json 定义团队统一的严格度和目标版本,各子包 extends 它后按需覆盖 outDir/rootDir/paths。也可以直接 extends 社区维护的基线包,如 @tsconfig/node20、@tsconfig/strictest。

相关

  • tsc — tsc 命令本体:常用命令、与构建工具分工、TS 7.0 原生编译器
  • package-json — 同为项目根配置文件,tsconfig.json 管类型/编译,package.json 管依赖/脚本
  • vite — Vite 读取 tsconfig 的 paths/target 但不做类型检查
  • vite-config-ts — Vite 自身的构建配置文件
  • README — Bun 运行时可直接执行 .ts 文件,同样遵循 tsconfig 的路径别名等配置