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 自动导入」说清楚,具体修改仍交给开发者结合上下文判断。
为什么要从 warn 开始
这种规则很适合先 warn,不适合一上来 error。
规则很重要,存量代码也通常不会一开始就干净。一次扫描可能扫出几十个、上百个 warning,其中有些属于当前模块,有些属于别人的模块,有些甚至是历史迁移中临时留下的边界写法。直接升成 error,会把所有人卡在同一个门口,最后很可能变成「为了过 lint 大批量机械修改」。
warn 更像一个探照灯。它先回答三个问题:
- 规则会扫出多少问题。
- 问题集中在哪些模块。
- 哪些可以本轮顺手改,哪些应该交给对应 owner。
这次第一次扫描后,新的两条规则一共留下 184 个 warning,其中 Vue API import 129 个、组件 import 55 个。分布最高的是账号、页面头部、个人身份、首页卡片和展示名这些区域。这个结果说明,规则确实能抓到问题,也说明它不适合在当前阶段直接变成 error。
所以策略是:先建立规则,再按风险拆开处理。Vue API import 属于机械清理,保留类型 import 后可以批量改;组件 import 要看导入名是否只在模板里使用,再把模板标签改成 Nuxt 自动导入名。chatroom、message 这类不是当前 owner 的模块先通过路径配置放过。
组件 import 规则怎么定边界
组件规则没有试图拦截所有组件 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。
Vue API import 规则只拦 value import
Vue API 规则也没有把 from 'vue' 一刀切掉。它只看 value import,并且只看配置名单里的 API。
需要提醒的是这种写法:
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 />。这不是简单删一行能解决的。
其次,测试环境不一定有 Nuxt 自动导入上下文。Vue Test Utils 单测里,真实 Nuxt 会自动注册的组件,可能需要在 global.components 里手动补 stub 或真实组件。删除 import 后,测试也要一起看。
再次,有些 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 的行为放在规则代码里处理、在后续规则测试环境完善时补上专门用例。测试要保护规则边界的长期可维护性,覆盖率数字只是附带结果。
总结
Nuxt 自动导入不是「少写几行 import」这么简单,它也是项目公开面的一部分。
如果团队决定按路径自动生成组件名、按 Nuxt 自动导入使用 Vue API,那就不要只靠口头约定。口头约定挡不住历史代码、复制粘贴和多 Agent 并行改动。更稳定的做法是:
- 用 ESLint 把规则变成可见 warning。
- warning message 里给出建议写法,而不是只说错了。
- 不急着 autofix,先让人 review 真实上下文。
- 通过配置保留 owner 边界,避免改到当前不负责的模块。
- 给规则自己补测试,让护栏也被护栏保护。
等存量逐步清掉之后,再把核心目录从 warn 升到 error。到那时,这条规则就不只是“提醒”,而是项目结构长期稳定的一部分。
