Dynamically loading SVG in Vue3 and Vite

Viewed 1214

I'm trying to convert my Vue2/Webpack app to Vue3/Vite.

In Vue2/Webpack, this works:

<div v-html="require('!!html-loader!../../assets/icons/' + this.icon + '.svg')"></div>

Html-loader is added with:

"html-loader": "1.3.2",

In Vue3/Vite this throws the error: ReferenceError: require is not defined

I've looked around for an example of doing this but don't see how to do this without knowing the name of the file at compile time. Is that possible?

3 Answers

You could also use Lazy Load Components technique with defineAsyncComponent from Vue3 - ref.

or import() like is shown by @tony19 in this answer:

<script>
const getServiceIcon = async iconName => {
  const module = await import(/* @vite-ignore */ `../assets/svg/${iconName}.svg`)
  return module.default.replace(/^\/@fs/, '')
}

export default {
  data() {
    return {
      icon: null,
      iconName: 'icon1', // icon1.svg
    }
  },
  watch: {
    iconName: {
      async handler(iconName) {
        this.icon = await getServiceIcon(iconName)
      },
      immediate: true,
    },
  },
}
</script>

<template>
  <button @click="iconName = 'icon2'">Change to another SVG</button>
  <img :src="icon" height="72" width="72" />
</template>

You can take a look at vite-svg-loader plugin, and load SVG files as Vue components.

Simply by creating a component where you will load the icons. Using composition API here.

<template>
    <i v-html="svg" />
</template>

<script lang="ts" setup>
    import { computed } from 'vue';

    const props = defineProps(['icon', 'src']);
    const path = props.src ? props.src : '';
    const file = `${path}icon-${props.icon}`;
    const modules = import.meta.glob('../../assets/icons/**/*.svg', { as: 'raw' });

    const svg = computed(() => {
        return modules['../../assets/icons/' + file + '.svg'];
    });
</script>

<style lang="scss" scoped></style>

This will allow you to even style your SVG using css, tailwind etc...

The usage:

<UiIcon icon="NAME" class="w-8 fill-current text-red-500"/>

Done.

Related