Skip to content

tsconfig 常用字段详解 ​

⚠️ 本文档标注了升级前后的行为差异。你机器上现在只剩一个版本了:

位置版本说明
tsc(全局)7.0.2Go 原生重写版
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 es2022
error 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
CommonJSNode10
Node16 / Node18 / Node20Node16
NodeNextNodeNext
PreserveBundler

常用值:

  • 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 的参数
strictNullChecksnull / undefined 不再能赋给任何类型
strictFunctionTypes函数参数逆变检查
strictPropertyInitialization类属性必须在构造函数里初始化
useUnknownInCatchVariablescatch (e) 的 e 是 unknown 而非 any(4.4+)
strictBuiltinIteratorReturn内置迭代器耗尽时返回 undefined(5.6+)

它不包含的(要单独开):

  • noUncheckedIndexedAccess
  • exactOptionalPropertyTypes
  • noImplicitOverride
  • noImplicitReturns
  • noPropertyAccessFromIndexSignature

可以单独关掉某一个:

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 --noEmit

noEmitOnError ​

含义:有类型错误时就不产出文件。默认 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 移除
moduleResolutionnode/node10/classic 移除
baseUrl移除,并入 paths
types默认改为 [](不再自动包含)
rootDir默认 ./
esModuleInterop不能设为 false
downlevelIteration移除
alwaysStrict恒为 true

另一个大坑:TS 7 不提供旧的 JS 编译器 API(新的要等 7.1)。所以依赖它的工具需要继续用 TS 6.0:

  • ts-node
  • ts-jest
  • ts-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