代码 自动导入组件的代价:unplugin-vue-components 的配置项与三个典型故障

2026-10-22 21:31:25

自动导入组件节省的 import 语句,代价在哪里:unplugin-vue-components 配置与问题记录

项目地址:https://github.com/unplugin/unplugin-vue-components

它解决的问题很窄:模板里写 ``,不需要 import、也不需要 components 注册,插件按需把组件引进来,没用的不打包。支持 Vue 3,组件和指令都能用,支持 Vite / Webpack / Rspack / Rollup / Rolldown / esbuild。本文只谈各配置项在实际项目里容易遇到的情况,以及遇到问题时的排查思路。

哪些项目不该使用

  • Nuxt 项目:README 明确说可能不需要这个插件,用 @nuxt/components。
  • Vuetify 项目:官方建议优先用 vite-plugin-vuetify 一类的官方插件,内置 resolver 只是备用方案。
  • 组件总量很少的项目:显式 import 的成本本来就低,引入插件反而让「这个组件从哪来」在编辑器里搜不到,新加入的人只能靠猜。
  • 官方已声明不再接受新的内置 resolvers,所以冷门 UI 库要自己写 resolver,这笔维护成本得先算进去。

安装与接入

npm i unplugin-vue-components -D

Vite:

import Components from 'unplugin-vue-components/vite'

export default defineConfig({
plugins: [Components({ /* options */ })],
})

Rollup / Rolldown / esbuild 从各自子路径导入,写法相同。Quasar 走 vitePlugins: [['unplugin-vue-components/vite', {}]]。

Webpack / Rspack 需要注意:29.1.0 之后移除了 CommonJS 支持。如果配置还在用 require,只能停留在老版本:

require('unplugin-vue-components/webpack')({})

配置项和对应的使用场景

dirs / globs / extensions

默认 dirs: ['src/components']、extensions: ['vue']、deep: true。只要目录结构常规就无需改动。

一旦写了 globs(默认 ['src/components/*.vue']),dirs、extensions、directoryAsNamespace 会被整体忽略,排除项用 ! 前缀。使用场景:组件分散在 src/views/**/widgets 这类多目录时,才需要改用 globs;只为了排除一两个文件而改用 globs,相当于放弃自动扫描,后续增加目录时还得继续改配置。

子目录想当命名空间前缀就开 directoryAsNamespace: true,再配合 collapseSamePrefixes 折叠文件夹名与组件名的重复前缀,否则 components/base/BaseButton.vue 会变成一长串名字。

resolvers

内置了 Ant Design Vue、Arco、Element Plus、Naive UI、PrimeVue、Quasar、TDesign、Vant、Varlet UI、Vuetify、VueUse 等一批 resolver。不支持的库自己写,componentName 一定是 CapitalCase:

Components({
resolvers: [
(componentName) => {
if (componentName.startsWith('Van'))
return { name: componentName.slice(3), from: 'vant' }
},
],
})

使用场景:UI 库升级换了包名、或内部组件库有命名前缀时,这是唯一能不改模板就完成适配的地方。注意 resolver 返回 undefined 表示不处理,不要在 resolver 里抛错。

dts

装了 typescript 默认开启,生成 components.d.ts。最常见的出错情况不是插件没有生成,而是文件生成了但没被用上:tsconfig.json 的 include 里没有加 components.d.ts,于是模板里组件全是 any 或不报错也不提示。

types

有些库的组件是全局注册的(典型是 vue-router 的 RouterLink / RouterView),插件不需要导入它们,但类型不完整。只补类型:

Components({
dts: true,
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
})

默认会自动探测已安装的受支持库;写 types: [] 则完全关闭。

excludeNames

默认 [/^Async.+/]。使用场景:全局异步组件、或插件检测不到的冲突组件。当两个来源提供了同名组件,而自动导入的组件覆盖了你手动注册的组件时,把这个名字加进 excludeNames,让它回到手动注册的路径。

syncMode

default 是 dev 用 append、build 用 overwrite。使用场景:开发时 components.d.ts 出现过删掉的组件残留、或者手动改过该文件导致 diff 一直不干净,可以显式设 overwrite 全量覆盖;反过来只想要追加、避免覆盖手写内容就用 append。

另外 version: 2.7 是自动探测的,一般不填;dumpComponentsInfo 设为 true 或路径会输出 ./.components-info.json,排查「到底注册了哪些组件」时很有用。

三个典型故障如何排查

dts 未生成。 检查是否安装了 typescript;检查输出路径是否在 tsconfig.json 的 include 范围内;跑一次 build(overwrite 模式下 build 会全量重写);再不行开 dumpComponentsInfo 看插件是否真的扫到了组件。

组件名冲突。 表现是模板渲染出了意料之外的组件。先用 .components-info.json 确认这个名字被哪个 resolver / 目录接管,再决定是加 excludeNames,还是调整 directoryAsNamespace 让命名空间区分开。

路由懒加载后组件被重复打包。 README 的规则是:父组件异步注册(或懒加载路由)时,自动导入的组件跟随父组件一起 code split。含义很直接:多个异步路由共用同一个自动导入组件,这个组件的代码就会进到每个路由 chunk 里。发现重复时不要指望插件帮你拆分,把这类共享组件改成显式 import,或用构建工具的 manualChunks 单独抽出去。

用法上没有变化的部分

模板里直接写组件名即可,不需要 import 和注册。tree-shakable,只用到的组件才进包。内置 resolvers 覆盖了主流 UI 库,配合 unplugin-icons 也能用。目录命名空间、指令支持这些属于默认能力,开了就有,不需要额外配置。

复制全文 生成海报 Vue3 Vite 自动导入 工程化 TypeScript

推荐文章

程序员茄子在线接单