🚀 “踩坑日记”:shadcn + Vite 在 Monorepo 中配置报错

问题介绍

ui.shadcn.com/docs/instal...

按照这个官方文档配置 shadcn + vite 项目后,遇到个错误:

按照官方文档配置,理应是没有错误的,但是我的项目特殊点就在于是一个 Monorepo 项目。

所以,当你在一个 Monorepo 里使用 TypeScript + ESLint(Flat Config,eslint.config.js)时,常会遇到下面这个解析错误:

sh 复制代码
Parsing error: No tsconfigRootDir was set, and multiple candidate TSConfigRootDirs are present:
  - /Users/.../packages/SearchChat 
  - /Users/.../packages/SearchChatUI
You'll need to explicitly set tsconfigRootDir in your parser options.
See: https://typescript-eslint.io/packages/parser/#tsconfigrootdireslint

项目背景

  • Monorepo 项目结构示例:
sh 复制代码
/Users/.../ui-common
├── apps/
│   └── react-jsx/
├── packages/
│   ├── ChatMessage/
│   ├── CustomIcons/
│   ├── DatePicker/
│   ├── DropdownList/
│   ├── EntityUI/
│   └── SearchChatUI/
└── pnpm-workspace.yaml
  • 每个包(例如 packages/SearchChatUI)通常都有自己的 tsconfig.json(含 referencestsconfig.app.json / tsconfig.node.json)、eslint.config.jspackage.json
  • ESLint 在启用类型感知规则(或需要类型信息的配置)时,会通过 @typescript-eslint/parser 加载 TypeScript Program,这需要明确告诉它:以哪个目录为根去解析 projecttsconfig*.json)。

错误现象

  • 在 Monorepo 根或任一包里运行 eslint,报错显示发现多个候选 TSConfigRootDir
  • 这是因为解析器试图自动探测 tsconfig 根目录,但同时看到了多个包的 tsconfig,于是拒绝继续。

原因分析

  • TypeScript-ESLint 的解析器需要一个"根目录"(tsconfigRootDir)来解释你提供的 parserOptions.project(即哪些 tsconfig*.json 参与构建类型信息)。
  • 在 Monorepo 中,如果没有明确为每个包设定独立的 tsconfigRootDir 与对应的 project,解析器会在工作区内"看见"多个包的 tsconfig,从而无法确定到底应该用哪个根,最终报错。

快速修复(针对单个包)

packages/SearchChatUI 为例,给它的 eslint.config.js 增加明确的 parserOptions.tsconfigRootDirparserOptions.project 即可。

js 复制代码
// packages/SearchChatUI/eslint.config.js
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
import { fileURLToPath } from 'node:url'
import path from 'node:path'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
      reactHooks.configs.flat.recommended,
      reactRefresh.configs.vite,
    ],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        // 关键设置:指向当前包目录,避免 Monorepo 下的多 tsconfig 混淆
        tsconfigRootDir: __dirname,
        // 指定当前包使用的 tsconfig 列表(路径相对于 tsconfigRootDir)
        project: [
          './tsconfig.json',
          './tsconfig.app.json',
          './tsconfig.node.json',
        ],
        sourceType: 'module',
      },
    },
  },
])

验证:

  • 进入包目录运行 npm run lint(保证命令在包内执行)
  • 预期不再出现 Parsing error

在 Monorepo 根统一配置的做法(推荐)

如果你倾向于在根目录放一个统一的 eslint.config.js,可以使用 "按包 override" 的方式,让每个包都明确自己的 tsconfigRootDirproject

示例(伪代码,按需调整包路径):

js 复制代码
// eslint.config.js at workspace root
import { defineConfig } from 'eslint/config'
import globals from 'globals'
import tseslint from 'typescript-eslint'
import js from '@eslint/js'
import { fileURLToPath } from 'node:url'
import path from 'node:path'

const rootDir = path.dirname(fileURLToPath(import.meta.url))
const pkg = (dir) => path.join(rootDir, 'packages', dir)

export default defineConfig([
  {
    files: ['packages/SearchChatUI/**/*.{ts,tsx}'],
    extends: [js.configs.recommended, tseslint.configs.recommended],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        tsconfigRootDir: pkg('SearchChatUI'),
        project: [
          path.join(pkg('SearchChatUI'), 'tsconfig.json'),
          path.join(pkg('SearchChatUI'), 'tsconfig.app.json'),
          path.join(pkg('SearchChatUI'), 'tsconfig.node.json'),
        ],
        sourceType: 'module',
      },
    },
  },
  {
    files: ['packages/SearchChat/**/*.{ts,tsx}'],
    extends: [js.configs.recommended, tseslint.configs.recommended],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        tsconfigRootDir: pkg('SearchChat'),
        project: [
          path.join(pkg('SearchChat'), 'tsconfig.json'),
          path.join(pkg('SearchChat'), 'tsconfig.app.json'),
          path.join(pkg('SearchChat'), 'tsconfig.node.json'),
        ],
        sourceType: 'module',
      },
    },
  },
  // 为其他包继续添加 overrides...
])

要点:

  • 每个包的 override 都拥有自己的 tsconfigRootDir
  • project 数组中的路径要基于该包目录。
  • 保持按包运行 eslint .(或通过 workspace 脚本定位到包)能减少路径解析混乱。

常见坑位与提示

  • project 路径必须相对于 tsconfigRootDir,不要写相对于工作区根的路径。

  • 若你使用的是 TypeScript-ESLint 的"类型感知"配置(例如 tseslint.configs.recommendedTypeChecked 或启用了需要类型信息的规则),一定要提供 tsconfigRootDirproject

  • 如果你不需要类型感知规则(为了更快的性能),可以只用非 type-checked 的推荐集,省略 project(但要权衡规则能力):

    js 复制代码
    extends: [tseslint.configs.recommended] // 非类型感知
    // 不设置 parserOptions.project
  • 在包内运行 npm run linteslint .)比在根随意运行更可控。

  • ESM 环境下,要用 fileURLToPath(import.meta.url) 获取当前文件路径来计算 __dirname


验证步骤

  1. 在目标包目录执行:
    • npm run lint
  2. 确认不再出现 "No tsconfigRootDir was set ... multiple candidate TSConfigRootDirs ..." 的错误。
  3. 如果还有包报同样错误,逐个为它们的配置添加 tsconfigRootDirproject

性能与类型感知

  • 类型感知规则需要构建 TypeScript Program,解析器会加载并分析 project 指定的 tsconfig;在大 Monorepo 中这可能较慢。
  • 推荐做法:
    • 只有在确实需要类型规则的包上开启 project
    • 使用按包 override 控制范围。
    • 结合 CI 分层执行(先非类型感知快速检查,再在关键包跑类型感知规则)。

小结

这个报错本质是 Monorepo 环境下 "类型规则需要明确上下文" 的提醒。只要为每个包设定清晰的 tsconfigRootDirproject,ESLint 就能准确地获取类型信息并稳定工作。按包划分 override 是根级统一配置的好方式;而在包内独立配置则更为直觉。


参考链接

相关推荐
木卫二号Coding1 天前
Docker-构建自己的Web-Linux系统-Ubuntu:22.04
linux·前端·docker
CHU7290351 天前
一番赏盲盒抽卡机小程序:解锁惊喜体验与社交乐趣的多元功能设计
前端·小程序·php
RFCEO1 天前
前端编程 课程十二、:CSS 基础应用 Flex 布局
前端·css·flex 布局·css3原生自带的布局模块·flexible box·弹性盒布局·垂直居中困难
天若有情6731 天前
XiangJsonCraft v1.2.0重大更新解读:本地配置优先+全量容错,JSON解耦开发体验再升级
前端·javascript·npm·json·xiangjsoncraft
2501_944525541 天前
Flutter for OpenHarmony 个人理财管理App实战 - 预算详情页面
android·开发语言·前端·javascript·flutter·ecmascript
打小就很皮...1 天前
《在 React/Vue 项目中引入 Supademo 实现交互式新手指引》
前端·supademo·新手指引
C澒1 天前
系统初始化成功率下降排查实践
前端·安全·运维开发
摘星编程1 天前
React Native + OpenHarmony:自定义useFormik表单处理
javascript·react native·react.js
2601_949593651 天前
基础入门 React Native 鸿蒙跨平台开发:BackHandler 返回键控制
react native·react.js·harmonyos
C澒1 天前
面单打印服务的监控检查事项
前端·后端·安全·运维开发·交通物流