Home  >  Q&A  >  body text

Vue 3 dynamically imports based on Props

I am using unplugin-icon to create an icon component, normally I can import e.g.

//script
import IconCopy from '~icons/prime/copy'
//template
<IconCopy/>

It works, but if we want to use another icon, it feels inconvenient to import one by one, so I created a dynamic component called Eunoicon.vue

<script setup>
const props = defineProps({
    icon : {type:String}
})
const from = `~icons/prime/${props.icon}`
const TheIcon = await import(/* @vite-ignore */from)
console.log('ti',TheIcon)
</script>
<template>
<TheIcon/>  
</template>

But when I try to import it into the component, it throws the error Uncaught (in Promise) TypeError: Unable to resolve module specifier '~icons/prime/copy'. Any suggestions for this approach or any icon library that provides a simple approach? I've tried vue font Awesome but it's still not as simple as I'd like.

P粉388945432P粉388945432337 days ago812

reply all(1)I'll reply

  • P粉396248578

    P粉3962485782023-11-17 10:34:12

    Unfortunately, it is currently not possible to create imports dynamically. I found myself with the same problem a few months ago. My solution was to treat the icons as SVG and create an import file attached to my project like this:

    interface SvgObject {
      [key: string]: Function;
    }
    
    export function importSvg(icon: string) {
      const svg = {
        "sliders-horizontal": () => {
          return '<line x1="148" y1="172" x2="40" y2="172" fill="none" /> <line x1="216" y1="172" x2="188" y2="172" fill="none" /> <circle cx="168" cy="172" r="20" fill="none" /> <line x1="84" y1="84" x2="40" y2="84" fill="none" /> <line x1="216" y1="84" x2="124" y2="84" fill="none" /> <circle cx="104" cy="84" r="20" fill="none" /> ';
        },
    }
    

    And create a view component as shown below, which will retrieve the icon corresponding to the given name with the help of props.

    <script setup lang="ts">
    import { computed } from "vue";
    import { importSvg } from "@/icons/importSvg";
    
    interface Props {
      icon: string;
    }
    
    const IconComponent = importSvg(props.icon);
    
    </script>
    
    <template>
      <span>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 256 256"
          :aria-labelledby="icon"
          v-html="IconComponent"
        ></svg>
      </span>
    </template>
    
    <style>
    ...
    </style>
    
    <MyIcon icon="sliders-horizontal"/>
    

    Of course, manually creating the import file would be cumbersome, so in my case I created a python script that takes the path to the SVG icons folder and processes each icon to shrink them and create the import file automatically. This script works with icons from the Phosphor Icon Library. You can find the code for the scripts and Vue components in the github repository:

    https://github.com/fchancel/Phosphor-icon-vue-component

    If you decide to use the Phosphor icon, don't hesitate to be inspired by it, modify it, or use it

    reply
    0
  • Cancelreply