编程 auto-imports.d.ts 已生成还报 Cannot find name 'ref':TS、ESLint 9、模板三处断链

2026-11-05 00:04:27

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。

原因通常是三个之一:

  1. tsconfig.app.json 的 include 只有 ["src/**/*", "src/**/*.vue", "env.d.ts"],根目录的 auto-imports.d.ts 不在程序范围内;
  2. CI 里 typecheck 跑在 build 之前,dts 还没写盘——dts 要跑一次 dev 或 build 才生成;
  3. 生成文件被 .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 排序规则的维护成本不一定更高。

推荐文章

程序员茄子在线接单