Nuxt 自动导入不该靠自觉:一次组件和 Vue API import 的 ESLint 护栏
Nuxt 的自动导入很好用,但它有一个隐蔽问题:只要团队里有人继续手写 import,代码仍然能跑,review 也未必会注意到。久而久之,同一套组件会同时出现自动导入名和相对路径 import;同一批 Vue API 也会混用直接调用和 import { computed, ref } from 'vue'。
这类问题单看一行不大。真正麻烦的是它会破坏项目的可理解性:组件路径一改,显式 import 跟着坏;目录层级重构后,旧的 deep import 会把子组件暴露面撑大;业务组件里堆满框架 API import,也会让读者误以为这些 API 不是项目约定的一部分。
这次给一个 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 自动导入」说清楚,具体修改仍交给开发者结合上下文判断。
这组检查现在已经沉淀到 @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。静态规则如果没有 owner 边界,很容易把「这个模块现在不归我改」变成「我顺手改了别人代码」。这在多人分支、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,而不是只做一次大范围字符串替换。
规则本身也要有测试
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 边界继续收窄。
规则页提供可编辑示例、接入配置、维护入口和测试位置,迁移时可以就近核对建议写法与实际组件名。
自动导入也是公开面
Nuxt 自动导入既减少显式 import,也定义了项目使用组件和 Vue API 的公开方式。
如果团队决定按路径自动生成组件名、按 Nuxt 自动导入使用 Vue API,那就不要只靠口头约定。口头约定挡不住历史代码、复制粘贴和多 Agent 并行改动。更稳定的做法是:
- 用 ESLint 把规则变成可见 warning。
- warning message 里给出建议写法,而不是只说错了。
- 建议组件名要以 Nuxt 真实生成结果为准,尽量复用或最小复制框架实现,不手写「差不多」的 resolver。
- 不急着 autofix,先让人 review 真实上下文。
- 通过配置保留 owner 边界,避免改到当前不负责的模块。
- 给规则自己补测试,让护栏也被护栏保护。
等存量逐步清掉之后,再把核心目录从 warn 升到 error,让自动导入约定成为必须通过的检查。