ESLint 怎样判断 Vue 组件的根 Block
在浏览器里看到 common-empty-state,可以顺着这个名字找到 components/common/empty-state.vue。但把这个文件移到普通 Vue 组件库后,根 Block 往往写成 empty-state。两个项目的文件路径可以一样,命名却取决于组件所在的工程。
给 ESLint 加一个 mode 就能区分两套写法。遇到 monorepo 时,麻烦在于同一个 ESLint 进程可能同时检查 Nuxt 应用和普通 Vue 包。按启动命令的目录猜框架,会把一个包的命名约定套到另一个包上。
先确定文件属于哪个项目
从正在检查的 SFC 所在目录往上走,遇到最近的 package.json 或 nuxt.config.* 就停止。当前边界有 Nuxt 配置,或自己的依赖声明包含 nuxt,就进入 Nuxt 分支。普通 Vue 子包有自己的 package.json,即使仓库根声明了 Nuxt,也会按子包自己的约定处理。
这一步只能判断框架。Nuxt 允许关闭 pathPrefix,也允许给扫描目录加 prefix。两个项目同样都用了 Nuxt,components/common/empty-state.vue 的注册名仍可能不同。 Nuxt 组件目录文档
用生成声明确定 Nuxt 的组件名
Nuxt 的准备步骤会生成类型声明。以默认构建目录中的 .nuxt/types/components.d.ts 为例,注册名和源码路径就在同一条属性声明里。下面只保留了其中一条映射。
interface _GlobalComponents {
// prettier-ignore
CommonEmptyState: typeof import('../../app/components/common/empty-state.vue')['default'];
}
// prettier-ignore 让 Prettier 跳过这条属性,保留 Nuxt 生成声明时的写法。按 Prettier 的打印规则,这里的类型会写成 (typeof import(...))['default']。这对括号的来由和四个工具的输出差异见《从一对 TypeScript 括号看 Prettier、Biome、dprint 和 Oxfmt》。
ESLint 用 TypeScript 解析声明,把相对导入路径解析成绝对路径,再用正在检查的文件查这个映射。上面的文件得到 CommonEmptyState,转成 kebab-case 就是 common-empty-state。配置改了前缀,重新生成的声明也会给出新的注册名。 Nuxt prepare 命令
缺少声明时,缩小判断范围
| 当前文件的条件 | 根 Block 的来源 |
|---|---|
| 普通 Vue 项目 | SFC 文件名 |
| Nuxt 有声明且能找到唯一注册名 | 生成的注册名 |
| Nuxt 尚未生成声明,且扫描约定可静态判断为默认 | 默认组件目录下的相对路径 |
| 有声明但文件未注册,或自定义扫描缺少可用声明 | 跳过诊断 |
没有声明时,规则只在默认目录内按路径推断。自定义组件扫描、模块或动态配置可能改变结果,先运行 nuxt prepare 就能让 Nuxt 自己解析这些设置。Lint 过程中只读文件,避免在编辑器每次检查时执行项目配置。
@sheng/vue-require-component-root-block 省略 mode 就走这套判断。需要固定规则时,mode: 'vue' 直接按文件名,mode: 'nuxt' 直接按 Nuxt 默认路径命名。实际测试把 Nuxt 应用和普通 Vue 组件库放在同一仓库里,用同名组件分别验证。Nuxt 声明重新生成后,同一个 ESLint 实例也能识别新的注册名。