tsconfig 常用字段详解
⚠️ 本文档标注了升级前后的行为差异。你机器上现在只剩一个版本了:
位置 版本 说明 tsc(全局)7.0.2 Go 原生重写版 node_modules/.bin/tsc(项目本地)7.0.2 已跟随升级,不再锁 5.8.3 下文用
[TS5]标出升级前(项目原先锁的 5.8.3)的行为,[TS7]标出 7.x 的行为 —— 也就是现在实际生效的那个。两者默认值差异巨大,升级后最容易踩的就是这里。
一、语言与目标
target
含义:编译产物使用哪个 ECMAScript 版本。决定哪些语法需要被降级(downlevel)。
默认值:
[TS5]ES5[TS7]最新稳定 ECMAScript 版本(es2025/es2026),随版本推进
为什么重要:这是对产物体积影响最大的一个字段。实测同一个文件:
ts
const a = obj?.b ?? 1;
const f = async () => { await Promise.resolve(); };
class C { x = 1 }[TS5] 默认(target=ES5)产出 40+ 行辅助函数:
js
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { ... };
var __generator = (this && this.__generator) || function (thisArg, body) { ... };
var a = (_a = obj === null || obj === void 0 ? void 0 : obj.b) !== null && _a !== void 0 ? _a : 1;[TS7] 默认原样保留,5 行:
js
const a = obj?.b ?? 1;
const f = async () => { await Promise.resolve(); };
class C { x = 1 }[TS7] 破坏性变更:target: es5 已被移除,写了直接报错:
error TS5108: Option 'target=ES5' has been removed. Please remove it from your configuration.最低支持 ES2015。ES5 是硬性要求的话只能留在 TS 6.0,或用 Babel/SWC 再降级一次。
常用值:ES2015 / ES2020 / ES2022 / ESNext
lib
含义:编译时能用哪些内置类型声明(Array、Promise、console、document 等)。只影响类型检查,不产生任何运行时代码。
默认值(不写 lib 时):target 对应的 ES 标准库 + DOM。
这点容易被搞错,实测确认:
ts
const t: string = document.title; // ✅ 不指定 lib 时通过console、document、window 都属于 DOM 规范,不是 ECMAScript 的,但它们默认就在。
真正的坑:一旦你显式写了 lib,默认值就被完全替换掉,DOM 也没了:
bash
tsc d.ts --lib es2022error TS2584: Cannot find name 'document'. Do you need to change your target
library? Try changing the 'lib' compiler option to include 'dom'.所以显式声明 lib 时,必须自己把 DOM 列上。
常用值:
- 浏览器项目:
["ES2022", "DOM", "DOM.Iterable"] - Node 项目:
["ES2022"](Node 的全局类型靠@types/node,不是lib)
注意:lib 只是类型层面。写 "DOM" 不代表运行时真有 document;反过来代码里写了 document 而 lib 没列 DOM,也只是类型报错不影响运行。
jsx
含义:如何处理 .tsx 里的 JSX 语法。
常用值:
| 值 | 行为 |
|---|---|
preserve | 保留 JSX 原样,交给后续工具(Babel/SWC)处理 |
react | 转成 React.createElement(...),需要 React 在作用域内 |
react-jsx | 转成 _jsx(...),自动从 react/jsx-runtime 引入,不需要手动 import React |
react-jsxdev | 同上,但带开发期调试信息 |
默认值:不设置。没有 .tsx 文件就不需要它。
二、模块
module
含义:产物用什么模块格式(ESM / CommonJS / ...)。
默认值:
[TS5]target为ES5时是CommonJS,否则ES6/ES2015[TS7]esnext
常用值:
esnext— 产出 ESM,交给打包器nodenext— 产出 Node 原生能跑的 ESM/CJS,配合package.json的type字段preserve— 原样保留模块语法(TS 5.4+)commonjs— 传统 CJS
[TS7] 破坏性变更:amd / umd / systemjs / none 已移除。实测接受的完整列表:
commonjs, es6, es2015, es2020, es2022, esnext,
node16, node18, node20, nodenext, preserve注意 commonjs 仍然可用。另外 amd / umd 报的错很有误导性:
error TS5095: Option 'bundler' can only be used when 'module' is set to
'preserve', 'commonjs', or 'es2015' or later.它抱怨的是 bundler,不是 amd——因为 amd 已经被踢出合法列表,导致 moduleResolution 的默认推导错乱。看到这个错先检查 module 的值。
⚠️ 本项目的坑:tsconfig.json 里是 "module": "commonjs",但 package.json 是 "type": "module"。产出会是这样:
js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var dep_ts_1 = require("./dep.ts");拿去跑直接崩:
ReferenceError: exports is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension
and 'package.json' contains "type": "module".改 --module esnext 即可。
moduleResolution
含义:import './x' 时,编译器去哪里找这个文件。
默认值:由 module 推导:
module | 默认 moduleResolution |
|---|---|
CommonJS | Node10 |
Node16 / Node18 / Node20 | Node16 |
NodeNext | NodeNext |
Preserve | Bundler |
常用值:
bundler— 给 Vite / webpack / esbuild 等打包器用,允许省略扩展名、支持exports字段nodenext— Node 原生 ESM,必须写全扩展名node10— 老式 Node 解析
[TS7] 破坏性变更:node / node10 / classic 已移除,只能用 nodenext / bundler。
rootDir / outDir
含义:
rootDir— 源码根目录,决定输出时的目录结构outDir— 输出目录
默认值:
outDir不指定 →.js落在.ts旁边(实测确认)rootDir→ 所有输入文件的最长公共路径;[TS7]默认./
注意:rootDir 不决定哪些文件参与编译,它只影响输出的目录结构。哪些文件参与由 files / include / exclude 控制。
[TS7] 变更:rootDir 默认 ./,所以有 src/ 的项目需要显式指定,否则输出会多套一层。
paths
含义:路径别名映射,让 import '@/utils' 这类写法能被解析。
json
{
"paths": { "@/*": ["./src/*"] }
}[TS7] 破坏性变更:baseUrl 已移除。paths 现在相对 tsconfig.json 所在目录解析。旧配置要迁移:
jsonc
// 之前
{ "baseUrl": ".", "paths": { "@/*": ["src/*"] } }
// 现在
{ "paths": { "@/*": ["./src/*"] } }可用 codemod 自动迁移:pnpm dlx ts6to7。
三、严格性
strict
含义:严格模式总开关,一次打开下面这一整族。
默认值:
[TS5]false[TS7]true
实测 [TS7] 默认下:
ts
function f(x) { return x }error TS7006: Parameter 'x' implicitly has an 'any' type.它包含的 9 个选项:
| 选项 | 作用 |
|---|---|
noImplicitAny | 禁止隐式 any |
noImplicitThis | 禁止隐式 any 的 this |
alwaysStrict | 每个文件产出 "use strict"([TS7] 恒为 true) |
strictBindCallApply | 严格检查 bind / call / apply 的参数 |
strictNullChecks | null / undefined 不再能赋给任何类型 |
strictFunctionTypes | 函数参数逆变检查 |
strictPropertyInitialization | 类属性必须在构造函数里初始化 |
useUnknownInCatchVariables | catch (e) 的 e 是 unknown 而非 any(4.4+) |
strictBuiltinIteratorReturn | 内置迭代器耗尽时返回 undefined(5.6+) |
它不包含的(要单独开):
noUncheckedIndexedAccessexactOptionalPropertyTypesnoImplicitOverridenoImplicitReturnsnoPropertyAccessFromIndexSignature
可以单独关掉某一个:
jsonc
{
"strict": true,
"strictPropertyInitialization": false
}noUncheckedIndexedAccess
含义:索引访问的结果自动加上 undefined。strict 不含这一项,需要单独开。
ts
const arr: string[] = [];
const s = arr[0]; // 类型是 string | undefined(开了之后)
s.toUpperCase(); // ❌ 报错:可能是 undefined严谨但很啰嗦,按需开启。
exactOptionalPropertyTypes
含义:区分「属性不存在」和「属性存在但值为 undefined」。
ts
interface Opt { a?: number }
const x: Opt = { a: undefined }; // ❌ 开启后报错,必须直接省略 a四、互操作
esModuleInterop
含义:允许用 import fs from 'fs' 这种默认导入语法去导入 CommonJS 模块,并生成 __importDefault 辅助函数。
默认值:false([TS7] 不能设为 false,恒为开)
配套的 allowSyntheticDefaultImports:只影响类型检查(允许默认导入不报错),esModuleInterop 同时影响类型和产出。开了前者不一定开后者,但反之必然。
isolatedModules
含义:保证每个文件能被独立转译,不依赖跨文件的类型推导。
为什么需要:esbuild / SWC / Babel 都是逐文件转译的,看不到全局类型信息。开了这个开关,TS 会提前拦住那些「单文件转译会出错」的写法。
例如 const enum 和只有类型没有值的 re-export:
ts
export { SomeType } from './types'; // ❌ 报错,转译器不知道 SomeType 是类型还是值
export type { SomeType } from './types'; // ✅只要用打包器就该开。
verbatimModuleSyntax
含义:不转换任何 import/export 语法,原样输出。所有类型导入必须显式写 import type。
ts
import { Foo } from './foo'; // Foo 会留在产物里
import type { Bar } from './bar'; // Bar 会被完全擦除比 isolatedModules 更严格、更可预测。适合纯 ESM 项目。和 esModuleInterop 一起用时要注意 CJS 互操作写法。
allowJs / checkJs
含义:
allowJs— 允许.js文件参与编译(会被复制/转译到outDir)checkJs— 对.js文件也做类型检查(需要先开allowJs),配合// @ts-check注释
默认值:均为 false
迁移场景:JS 项目渐进式转 TS 时打开。
resolveJsonModule
含义:允许 import data from './data.json'。
默认值:false
types
含义:只包含哪些 @types/* 包。
默认值:不设置时,自动包含 node_modules/@types 下的全部包。
[TS7] 破坏性变更:默认变成 [](不自动包含任何)。实测:
ts
console.log(process.env.HOME)error TS2591: Cannot find name 'process'. Do you need to install type definitions
for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field
in your tsconfig.需要显式声明:
json
{ "types": ["node"] }想恢复旧的自动包含行为,写 ["*"]。
五、输出
declaration
含义:生成 .d.ts 类型声明文件。
默认值:composite 为 true 时是 true,否则 false。发 npm 包必开。
sourceMap
含义:生成 .js.map,浏览器 DevTools 里能直接调试 TS 源码。
declarationMap
含义:为 .d.ts 生成 .d.ts.map,让「跳转到定义」能定位到 .ts 源文件。
noEmit
含义:只做类型检查,不产出任何文件。CI 里的标准用法:
bash
tsc --noEmitnoEmitOnError
含义:有类型错误时就不产出文件。默认 false——即使报错也会照常产出。实测中我见过报了一屏错但 .js 照样生成的情况。
removeComments
含义:产物里去掉注释。默认 false。
incremental / composite
incremental— 生成.tsbuildinfo缓存,二次编译更快composite— 项目引用(project references)的前置条件,强制declaration: true
六、完整性
skipLibCheck
含义:跳过所有 .d.ts 文件的类型检查。
默认值:false
强烈建议设为 true。原因:@types 包之间的类型冲突非常常见,而它们不是你的代码,你没义务修。
[TS5] 实际踩坑案例:本项目 tsconfig.json 里 target: es2016,命令行直接传文件时 lib 回落到 ES5,于是 @types/chai 刷出一屏:
node_modules/@types/chai/index.d.ts(882,42): error TS2552: Cannot find name 'ReadonlySet'.
node_modules/@types/chai/index.d.ts(895,49): error TS2583: Cannot find name 'WeakSet'.
node_modules/@types/chai/index.d.ts(1005,42): error TS2304: Cannot find name 'ReadonlySet'.
...注意:这些都是第三方包的类型定义在报错,跟你的代码无关。开了 skipLibCheck 就清净了。
[TS7] 下这个场景不会再现:命令行传文件时的默认 target 是 es2025,而且默认不自动加载任何 @types 包(见 types 一节),所以 chai 的类型根本不会被拉进来。实测 tsc --ignoreConfig 1.ts 不会出现这些报错。
forceConsistentCasingInFileNames
含义:import 路径的大小写必须和实际文件名一致。
默认 true,别关。macOS 文件系统不区分大小写,Linux 区分——不开这个会在本地跑得好好的,一上 Linux CI 就挂。
七、TS 7 破坏性变更速查
从 5.x/6.x 升到 7.x 时,以下都会直接报错或改变行为:
| 项目 | 变化 |
|---|---|
target | 默认改为最新稳定 ES 版本;es5 移除 |
strict | 默认 false → true |
module | 默认改为 esnext;amd/umd/systemjs/none 移除 |
moduleResolution | node/node10/classic 移除 |
baseUrl | 移除,并入 paths |
types | 默认改为 [](不再自动包含) |
rootDir | 默认 ./ |
esModuleInterop | 不能设为 false |
downlevelIteration | 移除 |
alwaysStrict | 恒为 true |
另一个大坑:TS 7 不提供旧的 JS 编译器 API(新的要等 7.1)。所以依赖它的工具需要继续用 TS 6.0:
ts-nodets-jestts-loader- Vue/Volar、MDX、Astro、Svelte 等框架集成
本项目的情况:依赖里没有 ts-node / ts-jest —— 测试跑的是 mocha + tsx,tsx 底层是 esbuild,不碰 TS 的编译 API,所以上面这条对本项目不适用。vitepress 确实在依赖里,但它的构建走 vite/esbuild,同样不依赖它。本项目已经跟着全局升到 7.0.2。
迁移工具:
bash
pnpm dlx ts6to7 # 自动改写 tsconfig,并打印需要人工确认的清单八、实战配置
Node ESM 项目(无打包器)
jsonc
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": ["node"],
"strict": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"sourceMap": true,
"declaration": true
}
}打包器项目(Vite / esbuild)
jsonc
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"noEmit": true
}
}只做类型检查的 CI
jsonc
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noEmit": true,
"skipLibCheck": true
}
}bash
tsc --noEmit附:命令行传文件时会忽略 tsconfig
这是最容易踩的坑。一旦你在命令行传了文件名,tsconfig.json 完全不被加载:
bash
tsc 1.ts # tsconfig.json 被忽略,全部走默认值
tsc # 不传文件,正常读取 tsconfig.json
tsc -p tsconfig.json # 显式指定[TS5] 是静默忽略,[TS7] 改成直接报错:
error TS5112: tsconfig.json is present but will not be loaded if files are
specified on commandline. Use '--ignoreConfig' to skip this error.按提示加上 --ignoreConfig 即可:
bash
tsc 1.ts --ignoreConfig