Automatic import
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
npm i -D unplugin-auto-import
- Add aut o 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/**'],
}),
]
//...
This will create file auto-imports.d.ts
containing auto imports for:
- vue, vue-router and i18n
- all composables for vuetify
- all composables from
@/composables
folder.
- 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, add route in card text, modify how card background is set.
<template>
<v-card :style="cardBackground('#00AA00')">
<v-card-title><v-icon icon="$mdiHome" />{{ t('sandbox.title') }}</v-card-title>
<v-card-text>{{ t('sandbox.content') }}</v-card-text>
<v-card-text>{{ t('sandbox.missing') }}</v-card-text>
<v-card-actions>
<v-btn color="primary">Primary</v-btn>
<v-btn color="secondary">Secondary</v-btn>
<v-spacer></v-spacer>
<v-btn @click="toggleTheme()">Toggle theme</v-btn>
</v-card-actions>
</v-card>
</template>
<script setup lang="ts">
const { t } = useI18n()
const theme = useTheme()
const cardBackground = useCardBackground
function toggleTheme() {
theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
}
</script>
Components
- Install unplugin-vue-components
npm 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({})
// ...
],
})
- Remove
import HelloWorld from './components/HelloWorld.vue'
from@/App.vue
to check that app still works.
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
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 default function AutoImportMdiIcons(options?: AutoImportMdiIconsOptions): Plugin | 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)
}
},
}
}
Options (AutoImportMdiIconsOptions
):
dirs
(optional):- Type:
string[]
- Description: An array of directories to search for files that may contain Material Design Icons. The default is
['./src']
. - Example:
dirs: ['./src/components', './src/views']
- Type:
exts
(optional):- Type:
string[]
- Description: An array of file extensions to look for when scanning files. The default is
['.vue', '.ts']
. - Example:
exts: ['.vue', '.js']
- Type:
pattern
(optional):- Type:
string
- Description: A string pattern used to detect icon references in the files. The default is
'$mdi'
, so it looks for identifiers like$mdiHome
,$mdiAccount
, etc. - Example:
pattern: '$mdi'
- Type:
outputPath
(optional):- Type:
string
- Description: The path where the generated TypeScript file that consolidates the icons will be written. The default is
./src/themes/
. - Example:
outputPath: './src/assets/icons/'
- Type:
outputFile
(optional):- Type:
string
- Description: The name of the TypeScript file that will be generated to import and export the icons. The default is
icons.ts
. - Example:
outputFile: 'mdi-icons.ts'
- Type:
log
(optional):- Type:
boolean
- Description: Whether or not to log the plugin's actions (e.g., scanning files, detecting icons). The default is
false
. - Example:
log: true
- Type:
- Add plugin to includes in
./tsconfig.node.json
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
// ...
"./src/plugins/icons.ts"
],
// ...
}
- Add plugin to
./vite.config.ts
// ...
import { AutoImportMdiIcons } from './src/plugins/icons'
// ...
export default defineConfig({
plugins: [
// ...
AutoImportMdiIcons({})
// ...
],
// ...
})
- Add new icon to
@/pages/sandbox/index.vue
and check that it is auto imported.
<!-- ... -->
<v-card-title><v-icon icon="$mdiHome" /><v-icon icon="$mdiHeart" />{{ t('sandbox.title') }}</v-card-title>
<!-- ... -->