Skip to content

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

  1. Install unplugin libraries
ps
npm i -D unplugin-auto-import
  1. Add aut o import configuration to vite.config.ts
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.
  1. Add this file to tsconfig.app,json
json
...
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "cypress", "./cypress.d.ts", "./auto-imports.d.ts"],
...
  1. Modify @/pages/sandbox/index.vue - remove all imports, add route in card text, modify how card background is set.
vue
<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

  1. Install unplugin-vue-components
ps
npm i unplugin-vue-components -D
  1. Add component auto import in ./vite.config.ts
ts
// ...
import Components from 'unplugin-vue-components/vite'
// ...
export default defineConfig({
  plugins: [
// ...
    Components({})
// ...
  ],
})
  1. 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.

  1. Create a plugin file
@/plugins/icons.ts
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']
  • 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']
  • 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'
  • 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/'
  • 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'
  • 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
  1. Add plugin to includes in ./tsconfig.node.json
json
{
  "extends": "@tsconfig/node20/tsconfig.json",
  "include": [
// ...
    "./src/plugins/icons.ts"
  ],
// ...
}
  1. Add plugin to ./vite.config.ts
ts
// ...
import { AutoImportMdiIcons } from './src/plugins/icons'
// ...
export default defineConfig({
  plugins: [
// ...
    AutoImportMdiIcons({})
// ...
  ],
// ...
})
  1. Add new icon to @/pages/sandbox/index.vue and check that it is auto imported.
vue
<!-- ... -->
<v-card-title><v-icon icon="$mdiHome" /><v-icon icon="$mdiHeart" />{{ t('sandbox.title') }}</v-card-title>
<!-- ... -->