TypeScript 6.0 升级检查清单:rootDir、types、baseUrl 退场前先把工程边界理顺
TypeScript 6.0 不是那种“多一个语法糖、少几个小 warning”的版本。它更像一次工程边界重整。官方在 6.0 release notes 里明确把它定义成通往 TypeScript 7.0 的过渡版本:6.0 仍然兼容 5.9 的大部分使用习惯,但会把一批旧默认值、旧选项和模糊行为收紧,让项目尽早暴露真实配置问题。
这对长期维护的前端和 Node.js 项目很重要。很多仓库这些年能编过去,不代表配置真的清晰,只是 TypeScript 之前替你兜底了。到了 6.0,strict、rootDir、types、baseUrl、moduleResolution 这些点开始要求你把话说清楚。
如果你的团队准备从 5.9 升级,最稳妥的做法不是直接改版本号后等 CI 爆红,而是先按一份固定清单排查。下面这份清单适合有 monorepo、别名路径、Node 类型声明、测试工具类型和构建产物目录的实际工程。
先理解 6.0 的信号
TypeScript 官方给出的方向很明确:现代项目默认运行在更“新”的 JavaScript 环境里,ESM 和 bundler 已经成为主流,很多过去为了兼容历史场景存在的推断和默认值,现在反而会制造歧义、性能损耗和“本地能过、运行时报错”的假象。
所以 6.0 的重点不是多了多少新语法,而是三件事:
- 让默认值更贴近现代工程。
- 让隐式推断减少,改成显式声明。
- 提前为 TypeScript 7.0 删除旧选项做准备。
如果你只是想“先升上去再慢慢改”,官方也给了缓冲办法:可以先在 tsconfig.json 里加上 "ignoreDeprecations": "6.0",但这只适合短期过渡,不能当长期方案,因为这些废弃项到了 7.0 不会再保留。
第一步:先在本地把基线跑通
升级前先固定环境,避免把 TypeScript 问题和 Node、包管理器差异混在一起。建议先做这几个动作:
npm install --save-dev typescript@6
npx tsc --version
npx tsc --noEmit --pretty false
如果仓库有多个 tsconfig,继续补两条:
npx tsc -p tsconfig.json --showConfig > tsconfig.expanded.json
npx tsc -b --verbose
这样做的目的是先看两个现实问题:
- 最终生效的配置到底是什么。
- 多项目引用链路里,究竟是哪一个
tsconfig在制造错误。
很多团队升级 TypeScript 时卡很久,不是因为问题难,而是因为根本没看清最终配置层叠结果。
第二步:优先处理默认值变化
6.0 有几项默认值变化会直接影响老项目。
strict 现在默认是 true
如果你的项目以前没有显式写 strict,升级后可能突然冒出一批空值、联合类型、索引访问相关错误。这个变化本身是合理的,但不适合靠猜测修。
建议分两类处理:
- 老仓库短期先显式写
"strict": false,保证升级切换可控。 - 有能力顺手收债的仓库,直接保留默认严格模式并逐项清理。
关键不是“要不要严格”,而是不要让默认值替你决定仓库策略。
module 默认变成 esnext
如果你的代码仍然假设 CommonJS 输出,或者有依赖构建脚本读取编译产物,升级前就要明确写出:
{
"compilerOptions": {
"module": "nodenext"
}
}
或者按前端构建场景改成与 bundler 一致的模式。不要继续依赖“没写就沿用旧习惯”。
target 默认变成当前年份的 ECMAScript 版本
这意味着默认目标会随版本前进,而不是固定在过去某个年代。对浏览器基线明确、Node 版本固定的团队,这通常是好事;对需要输出到旧环境的项目,则必须显式锁定目标,例如:
{
"compilerOptions": {
"target": "es2022"
}
}
第三步:检查 rootDir,别让产物目录悄悄多一层
这是 6.0 最容易让团队第一时间踩坑的点之一。官方把 rootDir 的默认行为改得更直白了:默认就是 tsconfig.json 所在目录,而不是像以前那样根据输入文件共同路径去猜。
如果你升级后发现 dist/index.js 变成了 dist/src/index.js,大概率就是这里出了问题。典型修法是显式声明:
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["./src"]
}
如果测试文件或脚本放在 tsconfig 目录外面,也要重新审视 rootDir 和 include 的组合。不要只盯着“能不能编过”,还要看输出目录结构是否影响 Docker 镜像、部署脚本、产物上传或运行入口。
第四步:立刻显式写 types
6.0 里最值得团队马上处理的一个变化,是 types 默认值变成空数组 []。这不是小修小补,而是构建性能和可预测性上的一次明显收紧。
以前很多项目默认把 node_modules/@types 里能扫到的类型全带进来,所以你即使没写 types,也可能在全局里直接拿到 process、describe、Buffer 之类名字。6.0 不再替你做这件事。
实际做法很简单:缺什么写什么。
{
"compilerOptions": {
"types": ["node", "jest"]
}
}
如果你看到下面这类报错:
Cannot find name 'process'Cannot find name 'describe'Cannot find module 'fs' or its corresponding type declarations
优先不要去怀疑业务代码,先看是不是该把对应类型包和 types 列表补上。
只有在临时救火、需要快速恢复 5.9 行为时,才考虑:
{
"compilerOptions": {
"types": ["*"]
}
}
但这只是回退开关,不是长期配置。长期看,显式 types 更稳,也更利于构建性能。
第五步:把 baseUrl 从“隐式查找根”改成“显式路径映射”
如果你的仓库长期用了:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@app/*": ["app/*"],
"@lib/*": ["lib/*"]
}
}
}
那 6.0 升级前就该开始重写。官方已经把 baseUrl 标成废弃,并明确建议大多数项目直接删除它,把前缀写进 paths。
更稳的写法是:
{
"compilerOptions": {
"paths": {
"@app/*": ["./src/app/*"],
"@lib/*": ["./src/lib/*"]
}
}
}
这样做的好处很直接:
- TypeScript 的路径解析和 bundler 运行时行为更一致。
- 不会再因为
baseUrl被当成 lookup root,误把某些运行时根本不存在的导入判定为“合法”。 - monorepo 里更容易看出别名真正指向哪里。
如果你过去真的把 baseUrl 当成全局查找根用,官方给出的替代方案是加一个 catch-all * 映射。但这种场景并不常见,大多数项目都应该借这次升级把路径别名写清楚。
第六步:清理已经落后的模块解析和输出模式
6.0 对几种老选项的态度很明确:该退就退。
moduleResolution: classic 已移除
如果仓库里还保留这个配置,别再犹豫,直接迁到现代模式。前端项目通常优先考虑 bundler,Node 项目通常优先看 nodenext。
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}
或者:
{
"compilerOptions": {
"moduleResolution": "nodenext"
}
}
outFile 已删除
还在用 outFile 拼接输出的仓库,本质上是在让 TypeScript 做 bundler 的工作。6.0 已经把这个口子关掉了,应该迁到 Vite、esbuild、Rollup、Webpack 这类真正负责打包的工具上。
esModuleInterop: false 和 allowSyntheticDefaultImports: false 不再是推荐路线
如果老代码里依赖那套更“原始”的导入行为,升级时要检查 CommonJS 包的导入方式,必要时改成默认导入,避免出现“类型能过、运行行为怪”的情况。
第七步:把升级验证做成固定脚本,而不是一次性人工排查
只改 tsconfig 还不够,升级完成后要把验证动作固化下来。一个够用的检查链路通常包括:
npx tsc --noEmit
npm test
npm run build
如果是 monorepo,再加上:
npx tsc -b --clean
npx tsc -b
同时重点看四类输出:
- 编译错误是否集中在类型声明缺失。
- 输出目录结构是否发生变化。
- 路径别名在测试、开发和生产构建里是否一致。
- CI 和本地是否出现不一致结果。
只要这四项还没看清,就不要急着宣布“TypeScript 6.0 升级完成”。
常见误区
先加 ignoreDeprecations,然后一直不回头
它适合过渡,不适合长期停留。否则等 TypeScript 7.0 真把旧选项删掉,团队会在更紧张的时间点补债。
只修报错,不复查构建产物
rootDir、module、paths 这类问题有时不会直接报错,但会改变产物结构或运行时解析结果。编译绿了,不代表上线稳了。
把 types: ["*"] 当成正式方案
这是临时恢复旧行为的办法,不是长期最优解。真正稳的工程做法仍然是显式列出所需类型。
总结
TypeScript 6.0 的升级价值,不只是为了跟上版本号,而是它逼着团队把工程边界讲清楚。rootDir 决定产物结构,types 决定全局类型可见性,baseUrl 退场意味着路径别名要更显式,classic 和 outFile 的退出则提醒你别再用过时模式硬撑现代工程。
如果你的项目准备在 2026 年继续长期维护,现在就把 6.0 当成一次配置体检会更划算。先把隐式行为改成显式配置,再去谈 TypeScript 7.0,升级成本会低很多,团队对仓库的理解也会更一致。