Skip to content

自动导入

自动导入 Vue 组件和 API,无需手动 import,提升开发效率。

unplugin-auto-import

基本信息

特点

  • ✅ 支持 Vite、Webpack、Rollup、esbuild 等多种构建工具
  • ✅ 自动导入 Vue 3 Composition API(ref、computed、watch 等)
  • ✅ 支持 Vue Router、Pinia 等生态库
  • ✅ 自动生成 TypeScript 类型声明
  • ✅ 支持自定义导入规则
  • ✅ 按需导入,不影响打包体积
  • ✅ 完善的 IDE 智能提示支持

安装

bash
pnpm add -D unplugin-auto-import
bash
bun add -D unplugin-auto-import
bash
npm install -D unplugin-auto-import
bash
yarn add -D unplugin-auto-import

Vite 配置

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      // 自动导入 Vue 相关函数
      imports: ['vue', 'vue-router', 'pinia'],
      // 生成类型声明文件
      dts: 'src/auto-imports.d.ts',
      // ESLint 支持
      eslintrc: {
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
        globalsPropValue: true
      }
    })
  ]
})

基础用法

配置完成后,无需手动导入即可直接使用 Vue API:

vue
<script setup lang="ts">
// 无需 import { ref, computed, watch } from 'vue'
// 直接使用
const count = ref(0)
const doubled = computed(() => count.value * 2)

watch(count, (newVal) => {
  console.log('count changed:', newVal)
})

const increment = () => {
  count.value++
}
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Doubled: {{ doubled }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

Vue Router 自动导入

typescript
// vite.config.ts
AutoImport({
  imports: [
    'vue',
    'vue-router'
  ],
  dts: 'src/auto-imports.d.ts'
})

使用示例:

vue
<script setup lang="ts">
// 无需 import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()

const goToHome = () => {
  router.push('/')
}

// 获取路由参数
const id = computed(() => route.params.id)
</script>

<template>
  <div>
    <p>当前路由: {{ route.path }}</p>
    <p>参数 ID: {{ id }}</p>
    <button @click="goToHome">返回首页</button>
  </div>
</template>

Pinia 自动导入

typescript
// vite.config.ts
AutoImport({
  imports: [
    'vue',
    'pinia'
  ],
  dts: 'src/auto-imports.d.ts'
})

使用示例:

vue
<script setup lang="ts">
// 无需 import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
const { username, isLoggedIn } = storeToRefs(userStore)

const login = () => {
  userStore.login('admin', 'password')
}
</script>

<template>
  <div>
    <p v-if="isLoggedIn">欢迎, {{ username }}</p>
    <button v-else @click="login">登录</button>
  </div>
</template>

自定义导入

typescript
// vite.config.ts
AutoImport({
  imports: [
    'vue',
    {
      // 从 axios 导入
      'axios': [
        ['default', 'axios'] // import axios from 'axios'
      ],
      // 从自定义工具库导入
      '@/utils/request': [
        'request',
        'get',
        'post'
      ],
      // 从 lodash-es 导入
      'lodash-es': [
        'debounce',
        'throttle',
        'cloneDeep'
      ]
    }
  ],
  dts: 'src/auto-imports.d.ts'
})

使用示例:

vue
<script setup lang="ts">
// 无需手动导入,直接使用
const searchQuery = ref('')

// 使用 lodash 的 debounce
const debouncedSearch = debounce((query: string) => {
  console.log('搜索:', query)
  // 使用自定义的 request 函数
  get('/api/search', { params: { q: query } })
}, 300)

watch(searchQuery, (newVal) => {
  debouncedSearch(newVal)
})
</script>

<template>
  <input v-model="searchQuery" placeholder="搜索..." />
</template>

目录自动导入

自动导入指定目录下的所有函数:

typescript
// vite.config.ts
AutoImport({
  imports: ['vue'],
  // 自动导入 src/composables 目录下的所有文件
  dirs: [
    './src/composables',
    './src/utils'
  ],
  dts: 'src/auto-imports.d.ts'
})

目录结构:

src/
  composables/
    useCounter.ts
    useUser.ts
  utils/
    format.ts

使用示例:

vue
<script setup lang="ts">
// 无需导入,直接使用 composables 中的函数
const { count, increment, decrement } = useCounter()
const { user, login, logout } = useUser()

// 直接使用 utils 中的函数
const formattedDate = formatDate(new Date())
</script>

VueUse 集成

typescript
// vite.config.ts
AutoImport({
  imports: [
    'vue',
    '@vueuse/core'
  ],
  dts: 'src/auto-imports.d.ts'
})

使用示例:

vue
<script setup lang="ts">
// 无需 import { useMouse, useLocalStorage } from '@vueuse/core'

// 直接使用 VueUse 的函数
const { x, y } = useMouse()
const counter = useLocalStorage('counter', 0)
const isDark = useDark()
const toggleDark = useToggle(isDark)
</script>

<template>
  <div>
    <p>鼠标位置: {{ x }}, {{ y }}</p>
    <p>计数器: {{ counter }}</p>
    <button @click="counter++">增加</button>
    <button @click="toggleDark()">切换主题</button>
  </div>
</template>

unplugin-vue-components

基本信息

特点

  • ✅ 自动导入组件,无需手动注册
  • ✅ 支持多种 UI 库(Element Plus、Ant Design Vue、Naive UI 等)
  • ✅ 按需加载,优化打包体积
  • ✅ 自动生成 TypeScript 类型声明
  • ✅ 支持自定义组件目录
  • ✅ 完善的 Tree-shaking 支持
  • ✅ 支持 Vite、Webpack、Rollup 等构建工具

安装

bash
pnpm add -D unplugin-vue-components
bash
bun add -D unplugin-vue-components
bash
npm install -D unplugin-vue-components
bash
yarn add -D unplugin-vue-components

Vite 配置

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'

export default defineConfig({
  plugins: [
    vue(),
    Components({
      // 自动导入 src/components 目录下的组件
      dirs: ['src/components'],
      // 组件的有效文件扩展名
      extensions: ['vue'],
      // 生成类型声明文件
      dts: 'src/components.d.ts'
    })
  ]
})

基础用法

配置完成后,无需手动导入和注册组件:

目录结构:

src/
  components/
    Button.vue
    Card.vue
    Header.vue

直接使用:

vue
<script setup lang="ts">
// 无需导入组件
// import Button from '@/components/Button.vue'
// import Card from '@/components/Card.vue'
</script>

<template>
  <div>
    <Header />
    <Card>
      <Button>点击我</Button>
    </Card>
  </div>
</template>

Element Plus 集成

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [ElementPlusResolver()]
    })
  ]
})

使用示例:

vue
<script setup lang="ts">
// 无需导入 Element Plus 组件
const handleClick = () => {
  ElMessage.success('操作成功')
}
</script>

<template>
  <div>
    <el-button type="primary" @click="handleClick">
      主要按钮
    </el-button>
    <el-input v-model="input" placeholder="请输入内容" />
    <el-table :data="tableData">
      <el-table-column prop="name" label="姓名" />
      <el-table-column prop="age" label="年龄" />
    </el-table>
  </div>
</template>

Ant Design Vue 集成

typescript
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [
        AntDesignVueResolver({
          importStyle: false // 使用 CSS
        })
      ]
    })
  ]
})

使用示例:

vue
<template>
  <div>
    <a-button type="primary">主要按钮</a-button>
    <a-input v-model:value="input" placeholder="请输入" />
    <a-table :columns="columns" :data-source="data" />
  </div>
</template>

Naive UI 集成

typescript
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [NaiveUiResolver()]
    })
  ]
})

使用示例:

vue
<template>
  <n-config-provider>
    <n-button type="primary">主要按钮</n-button>
    <n-input v-model:value="input" placeholder="请输入" />
    <n-data-table :columns="columns" :data="data" />
  </n-config-provider>
</template>

自定义组件目录

typescript
// vite.config.ts
Components({
  // 指定多个组件目录
  dirs: [
    'src/components',
    'src/views/components',
    'src/layouts'
  ],
  // 深度搜索子目录
  deep: true,
  // 组件名称转换
  directoryAsNamespace: true
})

目录结构:

src/
  components/
    common/
      Button.vue
    form/
      Input.vue

使用示例:

vue
<template>
  <div>
    <!-- 使用目录作为命名空间 -->
    <CommonButton />
    <FormInput />
  </div>
</template>

全局组件配置

typescript
// vite.config.ts
Components({
  dirs: ['src/components'],
  // 全局组件模式
  globalComponentsDeclaration: true,
  // 包含的文件模式
  include: [/\.vue$/, /\.vue\?vue/],
  // 排除的文件模式
  exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/]
})

组件别名

typescript
// vite.config.ts
Components({
  dirs: ['src/components'],
  resolvers: [
    // 自定义解析器
    (componentName) => {
      // 为组件添加前缀
      if (componentName.startsWith('My'))
        return { name: componentName.slice(2), from: '@/components' }
    }
  ]
})

完整配置示例

点击查看完整配置
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    
    // 自动导入 API
    AutoImport({
      imports: [
        'vue',
        'vue-router',
        'pinia',
        '@vueuse/core'
      ],
      resolvers: [ElementPlusResolver()],
      dts: 'src/auto-imports.d.ts',
      dirs: [
        './src/composables',
        './src/utils'
      ],
      eslintrc: {
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
        globalsPropValue: true
      }
    }),
    
    // 自动导入组件
    Components({
      dirs: ['src/components'],
      extensions: ['vue'],
      deep: true,
      resolvers: [ElementPlusResolver()],
      dts: 'src/components.d.ts',
      directoryAsNamespace: true
    })
  ]
})

配合使用示例

完整项目示例

点击查看完整代码
vue
<script setup lang="ts">
// 无需任何导入语句
// ref, computed, watch 来自 unplugin-auto-import
// UserCard, ProductList 来自 unplugin-vue-components
// useMouse, useLocalStorage 来自 @vueuse/core

const count = ref(0)
const doubled = computed(() => count.value * 2)
const { x, y } = useMouse()
const savedCount = useLocalStorage('count', 0)

// 监听变化
watch(count, (newVal) => {
  savedCount.value = newVal
})

const increment = () => {
  count.value++
}

const reset = () => {
  count.value = 0
}

// 用户数据
const users = ref([
  { id: 1, name: '张三', avatar: '/avatars/1.jpg' },
  { id: 2, name: '李四', avatar: '/avatars/2.jpg' }
])
</script>

<template>
  <div class="p-6">
    <!-- 使用自定义组件,无需导入 -->
    <Header title="自动导入示例" />
    
    <div class="grid grid-cols-2 gap-4 mb-6">
      <Card>
        <h3 class="text-lg font-bold mb-4">计数器</h3>
        <p>当前值: {{ count }}</p>
        <p>双倍值: {{ doubled }}</p>
        <p>保存的值: {{ savedCount }}</p>
        <div class="mt-4 space-x-2">
          <Button @click="increment">增加</Button>
          <Button @click="reset" type="danger">重置</Button>
        </div>
      </Card>
      
      <Card>
        <h3 class="text-lg font-bold mb-4">鼠标位置</h3>
        <p>X: {{ x }}</p>
        <p>Y: {{ y }}</p>
      </Card>
    </div>
    
    <!-- 使用 Element Plus 组件,无需导入 -->
    <el-card class="mb-6">
      <template #header>
        <span>用户列表</span>
      </template>
      <div class="space-y-4">
        <UserCard 
          v-for="user in users" 
          :key="user.id"
          :user="user"
        />
      </div>
    </el-card>
    
    <!-- 使用产品列表组件 -->
    <ProductList />
  </div>
</template>

TypeScript 支持

类型声明文件

配置后会自动生成类型声明文件:

typescript
// src/auto-imports.d.ts
// 自动生成,不要手动修改
declare global {
  const ref: typeof import('vue')['ref']
  const computed: typeof import('vue')['computed']
  const watch: typeof import('vue')['watch']
  // ... 更多
}

export {}
typescript
// src/components.d.ts
// 自动生成,不要手动修改
declare module 'vue' {
  export interface GlobalComponents {
    Button: typeof import('./components/Button.vue')['default']
    Card: typeof import('./components/Card.vue')['default']
    // ... 更多
  }
}

export {}

tsconfig.json 配置

json
{
  "compilerOptions": {
    "types": ["vite/client"]
  },
  "include": [
    "src/**/*",
    "src/auto-imports.d.ts",
    "src/components.d.ts"
  ]
}

ESLint 配置

解决 no-undef 错误

json
// .eslintrc.json
{
  "extends": [
    "./.eslintrc-auto-import.json"
  ]
}

或手动配置:

json
{
  "globals": {
    "ref": "readonly",
    "computed": "readonly",
    "watch": "readonly"
  }
}

最佳实践

1. 合理使用自动导入

vue
<script setup lang="ts">
// ✅ 推荐:常用的 Vue API 自动导入
const count = ref(0)
const doubled = computed(() => count.value * 2)

// ✅ 推荐:业务组件自动导入
// <UserCard /> 自动导入

// ⚠️ 注意:复杂类型还是建议手动导入
import type { User, Product } from '@/types'
</script>

2. 组织组件目录

src/
  components/
    common/        # 通用组件
      Button.vue
      Card.vue
    business/      # 业务组件
      UserCard.vue
      ProductList.vue
    layout/        # 布局组件
      Header.vue
      Footer.vue

3. 性能优化

typescript
// vite.config.ts
Components({
  // 只在开发环境生成类型声明
  dts: process.env.NODE_ENV === 'development' ? 'src/components.d.ts' : false,
  // 排除不需要的目录
  exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]dist[\\/]/]
})

4. 团队协作

typescript
// 统一配置文件
// config/auto-import.config.ts
export const autoImportConfig = {
  imports: [
    'vue',
    'vue-router',
    'pinia',
    '@vueuse/core'
  ],
  dirs: [
    './src/composables',
    './src/utils'
  ]
}

// vite.config.ts
import { autoImportConfig } from './config/auto-import.config'

export default defineConfig({
  plugins: [
    AutoImport(autoImportConfig)
  ]
})

注意事项

1. 类型声明文件

  • ⚠️ 生成的 .d.ts 文件应该提交到版本控制
  • ⚠️ 不要手动修改自动生成的类型文件
  • ⚠️ 确保 tsconfig.json 包含这些文件

2. ESLint 配置

  • ⚠️ 需要配置 ESLint 全局变量,避免 no-undef 错误
  • ⚠️ 生成的 .eslintrc-auto-import.json 应该提交到版本控制

3. IDE 支持

  • ⚠️ VSCode 需要安装 Volar 插件
  • ⚠️ 重启 IDE 以获取最新的类型提示
  • ⚠️ 如果类型提示不生效,尝试重新生成类型文件

4. 性能考虑

  • ⚠️ 不要导入过多不必要的库
  • ⚠️ 合理设置 dirs 避免扫描过多文件
  • ⚠️ 使用 exclude 排除不需要的目录

5. 命名冲突

  • ⚠️ 注意组件名称不要与 HTML 标签冲突
  • ⚠️ 避免自定义函数与自动导入的函数同名
  • ⚠️ 使用命名空间避免冲突

常见问题

1. 类型提示不生效

bash
# 删除类型文件重新生成
rm src/auto-imports.d.ts src/components.d.ts
# 重启开发服务器
pnpm dev

2. ESLint 报错 no-undef

json
// .eslintrc.json
{
  "extends": ["./.eslintrc-auto-import.json"]
}

3. 组件未自动导入

检查配置:

typescript
Components({
  dirs: ['src/components'], // 确保路径正确
  extensions: ['vue'],      // 确保扩展名匹配
  deep: true               // 启用深度搜索
})

4. 打包体积过大

typescript
// 使用按需导入
Components({
  resolvers: [
    ElementPlusResolver({
      importStyle: 'sass' // 或 false
    })
  ]
})