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,strictrootDirtypesbaseUrlmoduleResolution 这些点开始要求你把话说清楚。

如果你的团队准备从 5.9 升级,最稳妥的做法不是直接改版本号后等 CI 爆红,而是先按一份固定清单排查。下面这份清单适合有 monorepo、别名路径、Node 类型声明、测试工具类型和构建产物目录的实际工程。

先理解 6.0 的信号

TypeScript 官方给出的方向很明确:现代项目默认运行在更“新”的 JavaScript 环境里,ESM 和 bundler 已经成为主流,很多过去为了兼容历史场景存在的推断和默认值,现在反而会制造歧义、性能损耗和“本地能过、运行时报错”的假象。

所以 6.0 的重点不是多了多少新语法,而是三件事:

  1. 让默认值更贴近现代工程。
  2. 让隐式推断减少,改成显式声明。
  3. 提前为 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 目录外面,也要重新审视 rootDirinclude 的组合。不要只盯着“能不能编过”,还要看输出目录结构是否影响 Docker 镜像、部署脚本、产物上传或运行入口。

第四步:立刻显式写 types

6.0 里最值得团队马上处理的一个变化,是 types 默认值变成空数组 []。这不是小修小补,而是构建性能和可预测性上的一次明显收紧。

以前很多项目默认把 node_modules/@types 里能扫到的类型全带进来,所以你即使没写 types,也可能在全局里直接拿到 processdescribeBuffer 之类名字。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: falseallowSyntheticDefaultImports: false 不再是推荐路线

如果老代码里依赖那套更“原始”的导入行为,升级时要检查 CommonJS 包的导入方式,必要时改成默认导入,避免出现“类型能过、运行行为怪”的情况。

第七步:把升级验证做成固定脚本,而不是一次性人工排查

只改 tsconfig 还不够,升级完成后要把验证动作固化下来。一个够用的检查链路通常包括:

npx tsc --noEmit
npm test
npm run build

如果是 monorepo,再加上:

npx tsc -b --clean
npx tsc -b

同时重点看四类输出:

  1. 编译错误是否集中在类型声明缺失。
  2. 输出目录结构是否发生变化。
  3. 路径别名在测试、开发和生产构建里是否一致。
  4. CI 和本地是否出现不一致结果。

只要这四项还没看清,就不要急着宣布“TypeScript 6.0 升级完成”。

常见误区

先加 ignoreDeprecations,然后一直不回头

它适合过渡,不适合长期停留。否则等 TypeScript 7.0 真把旧选项删掉,团队会在更紧张的时间点补债。

只修报错,不复查构建产物

rootDirmodulepaths 这类问题有时不会直接报错,但会改变产物结构或运行时解析结果。编译绿了,不代表上线稳了。

types: ["*"] 当成正式方案

这是临时恢复旧行为的办法,不是长期最优解。真正稳的工程做法仍然是显式列出所需类型。

总结

TypeScript 6.0 的升级价值,不只是为了跟上版本号,而是它逼着团队把工程边界讲清楚。rootDir 决定产物结构,types 决定全局类型可见性,baseUrl 退场意味着路径别名要更显式,classicoutFile 的退出则提醒你别再用过时模式硬撑现代工程。

如果你的项目准备在 2026 年继续长期维护,现在就把 6.0 当成一次配置体检会更划算。先把隐式行为改成显式配置,再去谈 TypeScript 7.0,升级成本会低很多,团队对仓库的理解也会更一致。