Vue 3 Dynamic Import based on Props

Viewed 223

I am creating an icon component with unplugin-icon and in usual case i can import for example

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

it works but it feels unconvenient to import one by one if we want ot use another icon so i create a dynamic component named 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 this to a component it throw error Uncaught (in promise) TypeError: Failed to resolve module specifier '~icons/prime/copy'. Is any suggestion for this approach or any icon library that provide simple way something like . I've tried vue font awesome but still it's not as simple as i want.

3 Answers

Why don't you pass the icon itself? You import the icon on your parent component and declare it as a function on your props. Like this

   const props = defineProps({
      icon: {
       type: Function,
       default: undefined,
      }
   })

Then bind it as a component

<component
   class="h-5 w-5 text-gray-400 hover:cursor-pointer"
   :is="props.icon"
></component>

Your parent component needs to pass it like (In my case, I use hero icons which are imported as a component)

  :icon="yourImportedIcon"

I don't think you'll be able to dynamically import the icons but you can import them at the base of your application, which will make globally available.

I use Font Awesome, so the implementation may differ. See below:

FontAwesome.ts:

import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { library } from "@fortawesome/fontawesome-svg-core";
import {
    faUserSecret,
    faCommentAlt,
    faCog,
    faPowerOff,
    faMoon,
    faTrophy,
} from "@fortawesome/free-solid-svg-icons";

library.add(
    faUserSecret,
    faCommentAlt,
    faCog,
    faPowerOff,
    faMoon,
    faTrophy,
);

export default FontAwesomeIcon;

main.ts:

import { createApp, provide, h } from "vue";
import FontAwesomeIcon from "./assets/icons/fontAwesome";

import App from "./App.vue";

const app = createApp(App);
app.component("FontAwesomeIcon", FontAwesomeIcon);

Use icon:

<font-awesome-icon :icon="['fas', 'faTrophy']" />

Unfortunately, it is currently impossible to create imports dynamically. I found myself in the same problem a few months ago. My solution was to treat the icons as SVGs and to create an import file attached to my project that looked 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 to create a view component as below which thanks to a props would retrieve the icon corresponding to the given name.


<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 creating the import file by hand would be cumbersome, so in my case I created a python script that takes a path to an SVG icon folder and processes each of the icons to minify them and create my import file automatically. The script works for icons from the phosphor icons library. You can find the code of the script and the Vue component in the github repo:

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

Do not hesitate to be inspired, to modify it or to use it if you decide to use Phosphor icons

Related