DEV Community

Cover image for Vue.js + Vite - How to dynamically inline SVG's without 3rd party libraries
Victor Neves
Victor Neves

Posted on

Vue.js + Vite - How to dynamically inline SVG's without 3rd party libraries

Recently on a project that I'm working on with Vue 3 + Vite and using a Server Driven UI approach while building a basic link component that can include icons I started researching for tools that could help me, as we are using SVGs and in order to better manipulate them (e.g change the color on hover), we need to inline them.

The problem

When working with Server Driven UI you don't exactly the data that you will gonna render and in this particular case, we don't know which icon will be added, so it's "not possible" to import the asset on the component as there are several icons that can be rendered. Well, it's possible but you would have to import all of them on the component and create a computed method to check the icon name received on the response in order to grab the icon from the list of available options. As you can imagine having several icons is not the best approach for a maintainable system besides you will import all icons even not using them.

Problem There a 3rd party libraries that can help to dynamically inline SVGs. I have worked with one of them (svg-sprite) on a previous project. It's nice as in terms of syntax you have something like this when you need to inline an SVG:

<svg width="32" height="32">
    <use :xlink:href="checkedIcon"></use>
</svg>
Enter fullscreen mode Exit fullscreen mode

Basically, you pass the name of the icon that you want to render. Pretty easy right? Well, after setup everything up, yes but you will spend time for that, and every time that a new icon is added to the system, you will have to add the icon to a JSON file. As you can see it's almost the same thing as I have described where you have to import every icon on the component, of course in this case you inline an SVG wherever you need as this will gonna be available overall components.

Solution

After seeing this tip import shaderString from './shader.glsl?raw' on Vite docs I started testing some implementations to see if I could achieve the same result but dynamically. When appending the parameter "?raw" you basically have access to the content of the file, so when used with SVG files you have the code inside and not the file itself.

Code example

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

interface Props {
  url: string;
  text: string;
  hasIcon?: boolean;
  icon?: string;
  ariaLabel?: string;
  externalLink?: boolean;
  target?: string;
}

const linkData = defineProps<Props>()
const isExternalLink = computed(() => linkData.externalLink)
const displayIcon = computed(() => linkData.hasIcon)
const icon = computed(() => linkData.icon)
const target = computed(() => linkData.target || '_blank')
const ariaText = computed(() => linkData.ariaLabel ? linkData.ariaLabel : linkData.text)
const getSvgIcons = await fetch(`assets/images/icons/${icon.value}.svg?raw`)
const svgIcon = computed(() => {
  try {
    return getSvgIcons.data.value
  } catch (error: any) {
    throw new Error(error)
  }
})
</script>

<template>
  <a
    v-if="isExternalLink"
    :target="target"
    :href="url"
    :aria-label="ariaText"
    rel="noopener noreferrer"
    class="base-link"
  >
    <span class="base-link__item">
      <i
        v-if="displayIcon"
        class="base-link__icon"
        v-html="svgIcon"
      ></i>
      {{ text }}
    </span>
  </a>
  <router-link
    v-else
    :to="url"
    :aria-label="ariaText"
    class="base-link"
  >
    <span class="base-link__item">
      <i
        v-if="displayIcon"
        class="base-link__icon"
        v-html="svgIcon"
      ></i>
      {{ text }}
    </span>
  </router-link>
</template>
Enter fullscreen mode Exit fullscreen mode

Discussion (0)