tsconfig 调优与工程配置
tsconfig 调优与工程配置
写在前面
前面五篇讲了类型系统的方方面面。这一篇讲 tsconfig.json——TypeScript 项目的核心配置。
tsconfig.json 写得好,编译快 10 倍、错误发现早 10 倍;写得不好,程序员跟类型作斗争。本篇从 strict 全家桶开始,到路径别名、增量编译、project references,最后实战一个老项目的渐进式迁移。
一、tsconfig 基础
1.1 一个最小 tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
1.2 常见配置项分类
| 分类 | 项 |
|---|---|
| 语言版本 | target, lib |
| 模块系统 | module, moduleResolution, esModuleInterop |
| 类型严格度 | strict, noImplicitAny, strictNullChecks |
| 输出 | outDir, declaration, sourceMap |
| 优化 | incremental, composite |
| 工程 | paths, baseUrl, project references |
二、strict 全家桶
2.1 strict: true 包含了什么
{
"strict": true
}
等同于启用:
{
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"alwaysStrict": true,
"useUnknownInCatchVariables": true,
"noImplicitThis": true,
"noImplicitReturns": true
}
2.2 每个选项的含义
noImplicitAny
// 报错 - 参数隐式 any
function process(value) { ... }
// OK - 显式 any
function process(value: any) { ... }
// OK - 有具体类型
function process(value: string) { ... }
strictNullChecks
let x: string = "hello";
x = null; // 报错 - null 不能赋值给 string
// 必须显式
let y: string | null = "hello";
y = null; // OK
最常用、影响最大的选项——开启后,所有 null/undefined 都要明确处理。
strictFunctionTypes
// 参数严格逆变
type Handler = (a: Animal) => void;
const dogHandler: Handler = (d: Dog) => d.bark(); // 报错(逆变)
2.3 新项目:直接 strict: true
{
"compilerOptions": {
"strict": true
}
}
2.4 老项目:渐进式开启
// 第一阶段:只开 noImplicitAny
{
"compilerOptions": {
"noImplicitAny": true
}
}
// 第二阶段:开 strictNullChecks
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}
// 几个月后:全开
{
"compilerOptions": {
"strict": true
}
}
三、target 与 lib
3.1 target
编译产物的 JS 版本。
| target | 输出特性 |
|---|---|
| ES5 | 全部转 ES5,支持 IE11 |
| ES2018 | 可选链、BigInt,空值合并不转 |
| ES2022 | top-level await、class fields 不转 |
| ESNext | 最新 |
选 target 的原则:
目标浏览器 → 找最低版本 → 选 target
3.2 lib
{
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}
| lib | 提供 |
|---|---|
| ES2022 | ES2022 标准 API |
| DOM | window、document、fetch |
| DOM.Iterable | NodeList 等可迭代 |
| WebWorker | Web Worker API |
Node 项目不要加 DOM:
{
"compilerOptions": {
"lib": ["ES2022"],
"types": ["node"]
}
}
四、模块系统
4.1 module
编译产物的模块系统。
| module | 输出 |
|---|---|
| ESNext / ES2022 | import / export |
| CommonJS | require / module.exports |
4.2 moduleResolution
| 模式 | 适用 |
|---|---|
Bundler |
用打包器(Vite、Webpack、Rspack),推荐 |
Node / NodeNext |
Node.js 原生 ESM |
4.3 esModuleInterop
{
"compilerOptions": {
"esModuleInterop": true
}
}
99% 的项目都应该开——允许 import x from "commonjs-module"。
五、路径别名
5.1 配置
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@components/*": ["components/*"],
"@utils/*": ["utils/*"]
}
}
}
// 不需要
import { Button } from "../../../components/Button";
// 用别名
import { Button } from "@components/Button";
5.2 与打包器配合
Vite:
// vite.config.ts
export default {
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
};
tsconfig 的 paths 必须和打包器对齐——否则 TS 检查通过但打包报错。
六、增量编译与缓存
6.1 incremental
{
"compilerOptions": {
"incremental": true
}
}
生成 .tsbuildinfo 文件,记录上次编译信息。改完代码后编译快 50%+。
6.2 skipLibCheck
{
"compilerOptions": {
"skipLibCheck": true
}
}
跳过 .d.ts 文件的类型检查——node_modules 类型有问题不报错。几乎所有项目都该开。
七、Project References
7.1 是什么
Project References 把大型 TS 项目拆成多个子项目,每个子项目独立编译。
packages/
├── ui/
│ └── tsconfig.json # composite: true
└── app/
└── tsconfig.json # references: ["../ui"]
7.2 子 tsconfig
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist"
}
}
7.3 引用
{
"references": [
{ "path": "../ui" }
]
}
7.4 实战:monorepo 项目加速
全量编译: 120s (改一行代码)
Project References: 5s (只编改了的包)
八、其他常用配置
8.1 forceConsistentCasingInFileNames
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true
}
}
Linux 文件名大小写敏感,Windows/Mac 不敏感——开启后强制一致,避免 Linux 部署后找不到文件。
8.2 noUnusedLocals / noUnusedParameters
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
洁癖选项——消除无用变量和参数。
九、实战:老项目 tsconfig 渐进式迁移
9.1 项目背景
公司老项目 5 年历史,strict: false,几千个类型错误。
9.2 迁移策略
| 阶段 | 配置 | 耗时 |
|---|---|---|
| 基础配置 | skipLibCheck + incremental | 1 周 |
| noImplicitAny | 逐步消除隐式 any | 2 周 |
| strictNullChecks | 修 null/undefined | 3 个月 |
| 完整 strict | 全部开启 | 2 个月 |
9.3 最终配置
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"skipLibCheck": true,
"isolatedModules": true,
"incremental": true,
"baseUrl": "./src",
"paths": {
"@/*": ["*"]
}
}
}
十、本篇小结
| 配置 | 关键点 |
|---|---|
| strict | 新项目直接开,老项目渐进式 |
| target | 根据目标浏览器选 |
| module | ESNext + Bundler 是现代标配 |
| paths | 必须跟打包器对齐 |
| skipLibCheck | 99% 项目该开 |
| incremental | 必备 |