TypeScript 项目的编译器配置文件,JSON 格式(支持注释,实际是 JSONC)。定义类型检查规则、模块解析策略、编译目标和输出行为,tsc 和绝大多数构建工具(Vite/esbuild/webpack 的 TS 插件、ts-node 等)都会读取它来决定如何处理 .ts 文件。
核心字段
| 字段 | 说明 |
|---|---|
compilerOptions | 编译器行为配置,绝大部分字段都在这里 |
include | 需要编译的文件/目录 glob 列表 |
exclude | 排除的文件/目录,默认排除 node_modules |
extends | 继承另一个 tsconfig 文件,支持继承 npm 包(如 @tsconfig/node20) |
references | Project 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)? |
moduleResolution | TS 按谁的规则找 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 却在服务端才崩溃);前端项目基本都要加。
两种最常见的组合可作为记忆锚点:
| 场景 | target | module | moduleResolution | lib |
|---|---|---|---|---|
| 纯 Node 后端 | ES2022 | NodeNext | NodeNext | ["ES2022"](不含 DOM) |
| 前端/打包工具项目 | ESNext(或 ES2020) | ESNext | Bundler | ["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 的路径别名等配置