unplugin-auto-import 三处静态检查断链:vue-tsc 报 Cannot find name、ESLint 9 报 no-undef、模板里 ref 不解包
项目地址:
- unplugin-auto-import:https://github.com/unplugin/unplugin-auto-import
- 底层实现 unimport:https://github.com/unjs/unimport
自动导入省掉的是 import 行,代价是来源隐形。下面三处断链是同一类问题的三个面:transform 阶段把 import 注入了,静态检查阶段没看见。
基础配置与 dts 的来源
npm i -D unplugin-auto-import
// vite.config.ts
AutoImport({
imports: ['vue', 'vue-router'],
dts: true,
})
从 v0.8.0 起,unplugin-auto-import 的转换能力由 unimport 提供,Nuxt 的自动导入也基于 unimport,新功能主要在 unimport 里开发。
本地装了 typescript 时,dts 默认生成 ./auto-imports.d.ts,落在项目根目录:
export {}
declare global {
const ref: typeof import('vue')['ref']
}
这份声明只服务于编辑器和类型检查,真正的 import 语句是 transform 阶段注入的。两边对不上,就出现下面的症状。
症状 A:dts 已生成,vue-tsc 仍报 Cannot find name 'ref'
tsserver 里 ref 标红,npm run typecheck 报 error TS2304: Cannot find name 'ref',但项目根目录躺着 auto-imports.d.ts。
原因通常是三个之一:
tsconfig.app.json的include只有["src/**/*", "src/**/*.vue", "env.d.ts"],根目录的auto-imports.d.ts不在程序范围内;- CI 里 typecheck 跑在 build 之前,dts 还没写盘——dts 要跑一次 dev 或 build 才生成;
- 生成文件被
.gitignore忽略,本地能过,CI 挂。
修法按场景挑:
- 把 dts 指到已在 include 内的路径,比如
dts: 'src/types/auto-imports.d.ts'; - 或把生成文件提交进仓库;
- 或在 typecheck 前先跑一次构建/插件,保证文件存在。
tsconfig 里别只 include 这一份。components.d.ts 由组件自动导入那个插件生成,是另一个文件,要一起 include。
症状 B:ESLint 报 'ref' is not defined
现象是 no-undef 报错:'ref' is not defined。
AutoImport({ eslintrc: { enabled: true } }) 只会写出 .eslintrc-auto-import.json,这是 eslintrc 格式。ESLint 9 起 flat config(eslint.config.js)是默认格式,不再自动读 .eslintrc*,所以文件生成了,没人读它。
两种接法。
用 @eslint/eslintrc 的 FlatCompat 翻译:
import { FlatCompat } from '@eslint/eslintrc'
const compat = new FlatCompat()
export default [
...compat.extends('./.eslintrc-auto-import.json'),
]
或者把 eslintrc.filepath 改成 ./.eslintrc-auto-import.js,让插件输出 ESM,再 import 进 languageOptions.globals:
import autoImport from './.eslintrc-auto-import.js'
export default [
{
languageOptions: {
globals: autoImport.globals,
},
},
]
globals 必须放在 languageOptions 下面。把整份对象平铺到数组顶层不会生效,社区里「引入后所有 eslint 规则失效」的反馈多半是写法问题。
改配置之前:先确认 no-undef 从哪来
typescript-eslint 官方不建议开 no-undef,TS 编译器自己会查;@vue/eslint-config-typescript 的 recommended 里 no-undef 本来就是关的。
TS 项目里冒出 no-undef,先确认规则来源:是自己手写了 'no-undef': 'error',还是一批 JS 文件或纯 JS 项目带进来的。不要一上来就改自动导入配置。
版本时间线:rc 兼容路径有期限
flat config 自 v9.0.0 起是默认格式;v9 期间还能用 ESLINT_USE_FLAT_CONFIG=false 走旧 rc 配置,官方计划在 v10 移除 rc 支持。这条退路能用到 v9 结束。
症状 C:模板里 {{ counter + 1 }} 不生效
开了 AutoImport({ vueTemplate: true }) 后,模板里可以直接用自动导入的 API。但官方 README 写明了 caveat:自动导入的 ref 在模板里不会 inline 解包。{{ counter }} 可以,{{ counter + 1 }} 不行,得写 {{ counter.value + 1 }}。用 Volar 做类型检查能提前发现这类问题。
指令自动导入:v-loading 静默失效
模板里的 v-loading 这类指令需要 vueDirectives 选项,值为 true 或一个 resolver:
AutoImport({
vueDirectives: true,
resolvers: [ElementPlusResolver()],
})
ElementPlusResolver 会返回 type: 'directive' 的 resolver,但只有开启 vueDirectives,指令才会被注入模板上下文。只配了 resolvers 而没开 vueDirectives,表现是模板里指令静默失效,或运行时报 unknown directive。
dtsMode 默认 'append',旧声明会留着
删掉某个 preset 或改了包名之后,auto-imports.d.ts 里会残留旧条目。编辑器不报错,运行时才是 undefined。需要干净重写时显式设 dtsMode: 'overwrite'。
排查:先看注入清单
dumpUnimportItems 会把实际注入的清单导出成 JSON:
AutoImport({
dumpUnimportItems: './auto-imports.json',
})
先看清单再猜。ignore 和 ignoreDts 可以按名字禁用,ignoreDts 还能传正则。injectAtEnd 决定注入的 import 放在已有 import 之后还是最前面,和 ESLint 的 import/order、prettier 排序冲突时用得上。
与组件自动导入的边界
站内 #8140 讲的是组件自动导入:unplugin-vue-components 的 components.d.ts、resolver、懒加载重复打包。这里讲的是 API 自动导入在类型、ESLint、模板三处的静态检查断链,插件不同,失败面也不同。
取舍
自动导入省的是 import 行,代价是依赖来源隐形:新人搜不到 ref 从哪来,报错时先看到 Cannot find name,而不是「少了个 import」。小项目、多人协作、CI 常改的项目,显式 import 加 ESLint 排序规则的维护成本不一定更高。