Auto Import
Overview
Auto import in Vue refers to a feature that automatically imports components, plugins, modules or other resources into your Vue application without the need for manually specifying each import statement at the top of your files. This can greatly simplify codebase and reduce boilerplate, especially in large projects with many components.
There are side effects of using this technique. Some impact on build server start and reload time, as plugin scans all files. And dependencies are not clearly visible. Nevertheless, the positive impact on development speed is significant.
Routes
Check File Based Routing.
Composables
- Install unplugin libraries
pnpm i -D unplugin-auto-import- Add auto import configuration to
vite.config.ts
// ...
import AutoImport from 'unplugin-auto-import/vite'
// ...
plugins: [
// ...
AutoImport({
imports: [
'vue',
'vue-router',
'vue-i18n',
{
from: 'vuetify',
imports: [
'useDisplay',
'useDate',
'useDefaults',
'useDisplay',
'useGoTo',
'useLayout',
'useLocale',
'useRtl',
'useTheme',
],
},
],
dirs: ['./src/composables/**', './src/stores/**', './src/components/**'],
}),
]
//...This will create file auto-imports.d.ts containing auto imports for:
- vue, vue-router and i18n
- all composables for vuetify
- all composables from
@/composablesfolder.
- Add this file to
tsconfig.app,json
...
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "cypress", "./cypress.d.ts", "./auto-imports.d.ts"],
...- Modify
@/pages/sandbox/index.vue- remove all imports, it will still work.
NOTE
VsCode and dev restart might be needed for these changes to start working.
<template>
<v-card :style="cardBackground">
<v-card-title><v-icon icon="$mdiHome" />{{ t('sandbox.title') }}</v-card-title>
<v-card-text>{{ t('sandbox.description') }}</v-card-text>
<v-card-actions>
<v-btn @click="console.log('Primary!')" color="primary">Primary</v-btn>
<v-btn @click="console.log('Secondary!')" color="secondary">Secondary</v-btn>
<v-btn @click="settings.toggleTheme()">Toggle theme</v-btn>
</v-card-actions>
</v-card>
</template>
<script setup lang="ts">
import { useCardBackground } from '@/composables/ui'
import { useI18n } from 'vue-i18n'
import { useSettingsStore } from '@/stores/app/settings'
const cardBackground = useCardBackground('#0000ff')
const { t } = useI18n()
const settings = useSettingsStore()
</script>Components
- Install unplugin-vue-components
pnpm i unplugin-vue-components -D- Add component auto import in
./vite.config.ts
// ...
import Components from 'unplugin-vue-components/vite'
// ...
export default defineConfig({
plugins: [
// ...
Components({})
// ...
],
})- Test
Create @/components/TestComponent.vue
<template>I am a test component.</template>And use it anywhere, e.g. in @/App.vue without any importing.
<template>
<!-- -->
<test-component />
<!-- -->
</template>Icons
As on moment there is no native support yet - see Feature request, here is a custom plugin to generate icon includes based on $mdi pattern.
- Create a plugin file
@/plugins/icons.ts
source
import { promises as fs } from 'node:fs'
import type { Plugin, ResolvedConfig } from 'vite'
type AutoImportMdiIconsOptions = {
dirs?: string[]
exts?: string[]
pattern?: string
outputPath?: string
outputFile?: string
log?: boolean
}
export function AutoImportMdiIcons(options?: AutoImportMdiIconsOptions): Plugin {
let config: ResolvedConfig
const dirs: string[] = options?.dirs || ['./src']
const exts: string[] = options?.exts || ['.vue', '.ts', '.md']
const pattern = options?.pattern || '$mdi'
const outputPath = options?.outputPath || './src/themes/'
const outputFile = options?.outputFile || 'icons.ts'
const log = options?.log || true
function pluginLogger(message: string): void {
if (!log) return
const date = new Date()
const time = date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
hour12: true,
})
console.log(
'\x1b[90m' +
time +
'\x1b[0m' +
'\x1b[36m' +
' [Auto Import MDI Icons] ' +
'\x1b[0m' +
message,
)
}
async function autoImportVuetifyMdiIcons(sourcePath: string, destinationPath: string) {
if (sourcePath == destinationPath) return
if (
!exts.some((ext) => sourcePath.endsWith(ext)) ||
!dirs.some((dir) => sourcePath.includes(dir.replace('./', '/')))
)
return
try {
const sourceContent = await fs.readFile(sourcePath, 'utf-8')
if (sourceContent.includes(pattern)) {
try {
const newRegex = new RegExp(`\\${pattern}[a-zA-Z0-9]+`, 'g')
const newIcons = sourceContent.match(newRegex)
if (newIcons && newIcons.length > 0) {
let existingIcons: string[] = []
try {
const existingContent = await fs.readFile(destinationPath, 'utf-8')
const existingRegex = new RegExp(`${pattern.replace('$', '')}[a-zA-Z0-9]+`, 'g')
existingIcons = existingContent.match(existingRegex) || []
} catch {
await fs.writeFile(
destinationPath,
`/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// noinspection JSUnusedGlobalSymbols\n// Generated by vite-plugin-auto-import-mdi-icons\n\n` +
`import {} from "@mdi/js"\n\n` +
`export default {}\n`,
)
}
const existingIconsSet = new Set(existingIcons.map((icon) => '$' + icon))
const hasNewIcons = newIcons.some((icon) => !existingIconsSet.has(icon))
if (!hasNewIcons) return
const icons = [...new Set([...existingIcons.map((icon) => '$' + icon), ...newIcons])]
.sort()
.map((icon) => icon.replace('$', ''))
const content =
`/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// noinspection JSUnusedGlobalSymbols\n// Generated by vite-plugin-auto-import-mdi-icons\n\n` +
`import {\n\t${icons.join(',\n\t')}\n} from "@mdi/js"\n\n` +
`export default {\n\t${icons.join(',\n\t')}\n}\n`
await fs.writeFile(destinationPath, content)
pluginLogger(`added: ${newIcons.join(', ')}`)
}
} catch (error) {
console.error(`Error writing file at ${destinationPath}:`, error)
}
}
} catch (error) {
console.error(`Error reading file at ${sourcePath}:`, error)
}
}
return {
name: 'auto-import-mdi-icons',
configResolved(resolvedConfig) {
config = resolvedConfig
},
async handleHotUpdate({ file, server }) {
await autoImportVuetifyMdiIcons(
file,
server.config.root + outputPath.replace(/^\.\//, '/') + outputFile,
)
},
async buildStart() {
const iconsPath = config.root + outputPath.replace(/^\.\//, '/') + outputFile
async function scanDirectory(dir: string): Promise<void> {
try {
const entries = await fs.readdir(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = dir + '/' + entry.name
if (entry.isDirectory()) {
await scanDirectory(fullPath)
} else if (entry.isFile()) {
await autoImportVuetifyMdiIcons(fullPath, iconsPath)
}
}
} catch (error) {
console.error(`Error scanning directory ${dir}:`, error)
}
}
try {
for (const dir of dirs) {
const fullDir = config.root + '/' + dir.replace(/^\.\//, '')
await scanDirectory(fullDir)
}
} catch (error) {
console.error('Error during buildStart scan:', error)
}
},
}
}- Add plugin to
./vite.config.ts
// ...
import { AutoImportMdiIcons } from './src/plugins/icons'
// ...
export default defineConfig({
plugins: [
// ...
AutoImportMdiIcons({})
// ...
],
// ...
})- Add new icon to
@/pages/sandbox/index.vueand check that it is auto imported.
<!-- ... -->
<v-card-title><v-icon icon="$mdiHome" /><v-icon icon="$mdiHeart" />{{ t('sandbox.title') }}</v-card-title>
<!-- ... -->