Nuxt 自动导入不该靠自觉:一次组件和 Vue API import 的 ESLint 护栏
项目已经约定使用 Nuxt 自动导入,存量代码里却仍有不少手写 import。同一套组件既用自动导入名,也用相对路径 import;同一批 Vue API 既直接调用,也写 import { computed, ref } from 'vue'。两种写法都能运行,review 很容易漏掉约定不一致的地方。
混用的成本会在重构时暴露:组件路径一改,显式 import 需要同步更新;旧的 deep import 又让调用方依赖了子组件的内部路径。业务组件里重复声明框架 API import,也会掩盖项目统一使用自动导入的约定。
这次给一个 Nuxt 项目补了一组 ESLint 护栏:
app/components里的 Vue 组件,不再相对路径显式 import 另一个.vue组件。app/pages里的页面,不再显式 importapp/components下的.vue组件。- Nuxt 已经自动导入的常用 Vue 运行时 API,例如
computed、ref、watch、toRefs,不再从vue做 value import。 import type { Ref, ComputedRef } from 'vue'仍然允许,因为类型 import 不属于运行时自动导入。
这些规则都先用 warn,不做 autofix。规则在 lint 阶段把「这里应该交给 Nuxt 自动导入」说清楚,具体修改仍交给开发者结合上下文判断。
前提是项目已经启用对应的 Nuxt 自动导入扫描,当前文件也会经过该转换。独立工具包、脚本和普通单测不一定有 Nuxt 上下文,不能一起删除 import;组件名随扫描配置和路径变化,迁移仍需核对生成结果。
这组检查现在已经沉淀到 @sheng/eslint-plugin 的 nuxt-auto-import 分组里,对应规则是 @sheng/nuxt-no-explicit-vue-component-import 和 @sheng/nuxt-no-explicit-vue-api-import。项目接入时优先看规则文档和插件的使用指南,不要再从这篇文章里手动复制旧源码。
为什么要从 warn 开始
存量代码通常不会一开始就符合新规则。一次扫描可能出现几十个、上百个 warning,分散在不同 owner 的模块里,也可能包含历史迁移暂时保留的写法。直接使用 error,会阻塞当前工作,也可能推动开发者为通过 lint 批量机械修改。
先用 warn,可以确认扫描结果和迁移范围:
- 规则会扫出多少问题。
- 问题集中在哪些模块。
- 哪些可以本轮顺手改,哪些应该交给对应 owner。
这次第一次扫描后,新的两条规则一共留下 184 个 warning,其中 Vue API import 129 个、组件 import 55 个。分布最高的是账号、页面头部、个人身份、首页卡片和展示名这些区域。这个结果说明,规则确实能抓到问题,也说明它不适合在当前阶段直接变成 error。
规则建立后,按风险分别处理。Vue API import 属于机械清理,保留类型 import 后可以批量改;组件 import 要看导入名是否只在模板里使用,再把模板标签改成 Nuxt 自动导入名。chatroom、message 这类不是当前 owner 的模块先通过路径配置放过。
组件 import 规则怎么定边界
这次先约束组件目录内部的导入和页面对组件目录的导入。
第一个场景是 app/components 内部相对路径 import .vue 文件:
// 不推荐
import Avatar from './Avatar/index.vue'
如果这个文件位于 app/components/common/Account/Overview/Contacts/Item/index.vue,Nuxt 本来会根据路径生成组件名。调用方可以直接在模板里写:
<CommonAccountOverviewContactsItemAvatar />
这样组件名来自路径语义,重构时也更容易看出公开使用面。
第二个场景是 app/pages 显式 import app/components 下的组件:
// 不推荐
import FeedSection from '~/components/home/FeedSection/index.vue'
页面如果要用全局组件,同样应该直接使用 Nuxt 自动导入名:
<HomeFeedSection />
规则会根据 import 的真实路径推导建议组件名,并把它写进 warning message。它不做 autofix,因为删除 import 后还要确认模板里是否已经使用正确组件名、测试环境是否有 Nuxt 组件自动导入 mock、以及这个 import 是否被 TypeScript 类型推导借用了。这些都需要人或 Agent 做一次语义 review。
组件名推导要贴着 Nuxt 实现
组件 import 规则还有一个后来补上的边界:warning message 里给出的建议名,必须和 Nuxt 真正生成的组件名一致。
最容易踩坑的是按路径硬拼 PascalCase。例如组件放在 app/components/common/User/Profile/Record/RecordRow.vue 时,Nuxt 会把路径前缀和文件名按大小写拆段,并处理重复片段。模板里应该写的是:
<CommonUserProfileRecordRow />
如果自己写一个「看起来差不多」的路径转换函数,很容易猜成:
<CommonUserProfileRecordRecordRow />
这类错误很隐蔽。ESLint warning 仍然会出现,建议文案也像是对的,但开发者按建议删掉 import、改完模板后,运行时才发现模板标签对不上 Nuxt 的自动导入名。
建议名的推导需要跟随 Nuxt 实现。有公开 API 时优先复用;当前没有公开导出的内部逻辑,只复制最小 helper,并在注释里标清对应的 Nuxt 版本。大小写拆分和 PascalCase 转换继续使用 Nuxt 依赖的 scule,避免公共转换规则也和框架漂移。
测试也要对照 Nuxt 产物。.nuxt/components.d.ts 记录的是 Nuxt 实际生成的自动导入组件名,专项测试应该优先读这个文件;只有在编辑器或局部测试没有生成 .nuxt 时,才退回本地 resolver。这样 Nuxt 升级后如果组件名规则变了,测试会先提醒规则同步,而不是让错误建议继续混进业务代码。
Vue API import 规则只拦 value import
Vue API 规则只检查配置名单里的 value import。
需要提醒的是这种写法:
import { computed, ref, watch } from 'vue'
在 Nuxt app 代码里,computed、ref、watch 这些运行时 API 已经自动导入,组件可以直接用:
const keyword = ref('')
const trimmedKeyword = computed(() => keyword.value.trim())
但类型 import 仍然应该保留:
import type { Ref } from 'vue'
运行时代码可以由 Nuxt 自动导入,类型系统仍然需要明确来源。拦截类型 import 会让开发者在 Ref、ComputedRef、MaybeRef 这类类型上绕路,降低可读性。
规则内部也保留了 names 配置。默认名单覆盖常用 Vue API;如果某个项目只希望约束一部分,或者后续 Nuxt 自动导入能力变化,可以通过 ESLint 配置调整,不需要改规则源码。
配置要留出 owner 边界
多个 owner 并行维护模块时,规则的扫描范围要和当前迁移范围对应。否则一次 lint 清理就可能涉及其他人正在修改的模块,在频繁 rebase 或业务迁移期间增加冲突。
这次配置里把 chatroom 和 message 先放过:
const AUTO_IMPORT_RULE_IGNORED_PATH_PATTERNS = [
'^app/components/(chatroom|message)/',
'^app/pages/(chatroom|message)(/|\\.)',
]
等对应模块开始整理,再把它们从忽略名单里拿出来。规则可以先进入主干,按 owner 逐步推进,不必等全项目一次性清零。
这里还有一个小经验:这类名单最好配置化,不要硬编码到规则里。规则负责识别问题,项目配置负责决定当前扫描范围。两层分开后,规则可以复用,项目也能按 owner、路径、迁移阶段逐步推进。
为什么不做 autofix
很多 ESLint 规则可以 autofix,比如排序、空格、简单语法替换。但自动导入这类规则不太适合第一版就做 autofix。
删除组件 import 后,至少还有三处需要核对:
- 模板名可能要同步修改。原来 import 叫
Avatar,模板里写<Avatar />,自动导入名却可能是<CommonAccountOverviewContactsItemAvatar />,单删一行无法完成迁移。 - Vue Test Utils 单测未必有 Nuxt 自动导入上下文,可能需要在
global.components里补 stub 或真实组件。 - import 可能用于
InstanceType<typeof ChildComponent>这样的类型推导。是否改成显式 expose interface,还是保留特殊 import,要结合组件契约判断。
所以第一版 warning message 只给建议写法,不直接改代码。它告诉人和 Agent:「这行应该删,模板里应该用哪个 Nuxt 自动导入组件名」。真正改的时候仍然要看上下文。
存量代码怎么改
这类规则落地时,最好不要追求第一天清零。更稳的流程是:
- 规则先以
warn进入。 - 全量扫一遍,按模块、owner 和风险分桶。
- 当前正在改的模块先顺手修。
- 每修一块,都补规则测试、组件测试或 source 级断言。
- 存量 warning 少到可以控制时,再考虑把核心范围升成 error。
这次实际改法也是这样:先补规则和 RuleTester 测试,再扫全量 warning;随后先批量处理 Vue API value import,再逐个 review 组件 import 是否只用于模板。最终在当前配置范围内把新规则 warning 清到 0,非当前 owner 的 chatroom、message 仍然通过配置忽略,后续等 owner 明确后再推进。
按模块推进,能控制修改范围并逐块验证:
- 不会因为一条新规则把大量不相关业务文件卷进当前 diff。
- 每个被修改的模块都能跟着跑自己的单测、类型检查和 lint,而不是只做一次大范围字符串替换。
规则要分别验证路径归属、组件名推导、value/type import 与忽略项,不能只测一个能够触发 warning 的例子。完整内容见自动导入规则的验证边界弹窗。
总结
Nuxt 自动导入定义了项目使用组件和 Vue API 的公开方式。这组规则把约定落在两类明确场景上:应用内组件改用 Nuxt 生成的组件名,已自动导入的 Vue 运行时 API 去掉名单内的 value import;类型 import 和没有 Nuxt 转换上下文的代码仍需保留自己的依赖来源。
护栏先以 warn 进入,按路径和 owner 分批迁移。组件建议名必须对得上 Nuxt 的实际产物,修改时还要核对模板、测试和类型推导,因此保留人工 review、暂不做 autofix;规则测试也要保护路径、类型与建议名的边界。
当前配置范围内的 warning 已经清到 0,chatroom、message 仍是等待对应 owner 推进的例外,不能把局部清零当成全项目迁移完成。核心范围是否从 warn 升到 error,还要看存量整理进度和剩余告警是否可控。
自动导入规则的验证边界
规则本身也要有测试
ESLint 规则是代码,也会退化。尤其这种规则需要同时识别路径、import 类型和建议文案,不能只靠手工扫一遍。
RuleTester 至少要覆盖这些情况:
app/components里的相对.vueimport 会 warning。app/pages里从~/components/...import.vue会 warning。- 普通 helper import 不 warning。
chatroom、message这类配置忽略路径不 warning。computed、ref、toRefs等 Vue API value import 会 warning。import type不 warning。
这里有一个现实细节:如果测试环境没有 TypeScript parser,import type 的测试可能需要额外接入 parser,或者先把类型 import 的行为放在规则代码里处理、在后续规则测试环境完善时补上专门用例。测试要保护规则边界的长期可维护性,覆盖率数字只是附带结果。
两条规则分别处理组件路径和 Vue API 名单。
第一块是组件 import:src/rules/nuxt-no-explicit-vue-component-import/index.ts 先把当前文件和 import source 都归一成相对 cwd 的 POSIX 路径,再只处理三类明确场景:app/components 内相对导入 .vue、app/components 内通过别名导入 app/components 下的 .vue,以及 app/pages 导入 app/components 下的 .vue。建议组件名由 src/rules/utils/nuxt-component-name.ts 推导,避免按路径硬拼出和 Nuxt 不一致的名字。
第二块是 Vue API import:src/rules/nuxt-no-explicit-vue-api-import/index.ts 只看 from 'vue' 的 named import,只报告配置名单里的 value import。import type 会被放过,names 和 ignoredPathPatterns 也留给项目按自动导入能力和 owner 边界继续收窄。
规则页提供可编辑示例、接入配置、维护入口和测试位置,迁移时可以就近核对建议写法与实际组件名。