Monorepo 中如何正确共享 TypeScript 配置?
我在用 pnpm 搭建的 Monorepo 项目里,想让多个子包共享同一套 tsconfig.json,但每次改完 root 的配置,子包里的类型检查就报错,比如找不到模块或者路径别名解析失败。
我试过在子包里用 "extends": "../../tsconfig.base.json",也配了 paths 和 baseUrl,但 VS Code 还是提示类型错误,build 时也出问题。是不是还要配什么编译选项或者引用方式不对?
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@utils/*": ["packages/utils/src/*"]
}
}
}
按照规范,问题出在你子包里直接配了
"baseUrl": ".",这会让 paths 的解析起点变成子包目录,而不是 root 目录。TypeScript 的 paths 是相对于 baseUrl 解析的,所以你配的@utils/*实际上是在找packages/utils/src/packages/utils/src/*,当然找不到。正确的做法是这样:
根目录的
tsconfig.base.json放公共配置:子包的
tsconfig.json继承时,不要覆盖 baseUrl:如果你的子包需要引用其他包的类型,建议按照 TypeScript 官方推荐的方式配置 Project References。根目录加一个
tsconfig.json:子包里也要加上
composite: true和对应的 references:另外 pnpm 的 workspace 依赖要用
workspace:*协议,确保依赖能正确解析。还有一点,VS Code 有时候缓存不会立刻刷新,改完配置后跑一下tsc --build看看命令行是否正常,命令行没问题那就是 VS Code 的缓存问题,重启一下或者执行 TypeScript: Restart TS Server。tsconfig.json的composite和references机制,以及每个子包自身的tsconfig.json正确引用 root 的配置,才能让 tsc 和 VS Code 都认得路径别名。我用 pnpm + ts + monorepo 的经验说,正确的做法分三步走:
第一步,root 下的 tsconfig.base.json 要严格限制内容,它只能放通用配置,不能包含
files、include、exclude,因为这些会覆盖子包的扫描范围;同时paths要写绝对路径,不能用相对路径,因为子包运行时的工作目录不同,相对路径会崩。你那个"@utils/*": ["packages/utils/src/*"]就是错的——它在 root 看没问题,但在packages/frontend里跑 tsc 时,它会把.当成当前子包目录,自然就找不到packages/utils/src/*。所以 root 的
tsconfig.base.json应该这样写:注意这里
composite: true必须有,它让这个 tsconfig 能被其他项目引用;baseUrl: "."是必须的,它代表当前 monorepo 根目录;paths里的路径一定要加./前缀,否则 tsc 会把它当成绝对路径,但又不是系统绝对路径,而是相对于baseUrl的路径,所以./packages/...才是安全写法。第二步,每个子包(比如
packages/frontend)都要有自己的tsconfig.json,并且用extends引用 root 的 base 配置,同时补全include和references:这里有两个关键点:
第一个是
extends路径要准确,从当前子包目录往回找;第二个是
references不能少,它告诉 tsc 这个包依赖了哪个子包(这里是../utils),tsc 才会先编译被依赖的包,并正确解析它的类型导出。如果你不写references,即使paths生效了,tsc 也会报错说找不到模块,因为它根本没去读utils包的tsconfig.tsbuildinfo文件。第三步,确保每个子包的
package.json里type字段和main/types字段匹配,比如:否则即使 tsc 编译成功,运行时也可能因为模块格式不匹配(比如你用了 ES module 但
main指向的是 cjs 文件)导致类型解析失败。另外,VS Code 的 TypeScript 插件默认不会加载
references,你需要在 VS Code 设置里开这个选项:或者更稳妥的做法是在项目根目录建一个
.vscode/settings.json:因为 pnpm 会把 typescript 装在
.pnpm/里,VS Code 如果找不到正确的 typescript 版本,就可能用系统自带的旧版,路径解析逻辑不一样,容易出错。最后说个常见坑:如果你用
ts-node或tsx这类运行时工具,它们默认不走tsconfig.json的paths,除非配合tsconfig-paths或module-alias。比如:或者在
tsconfig.json里加"moduleResolution": "bundler"(TypeScript 5.0+ 支持),这样运行时也能正确解析路径别名,前提是你的构建工具(比如 vite、webpack)也支持这个模式。总结一下,Monorepo 共享 tsconfig 的核心逻辑是:
root 只定义基础配置 +
composite: true+paths用相对根目录的路径每个子包用
extends引用 root +references声明依赖 +include控制扫描范围最后确保运行时工具也能识别这些配置
我之前踩过好多次坑,后来发现只要
references和composite都配齐,90% 的类型错误都能解决,剩下的就是路径写错或者子包没 build 完就引用了。