文章

Simple loading svg inline in Vue

Our goal is to do this:

<icon src=”plus.svg” />

So it rendered in DOM as

<span class=”icon”><svg>….</svg></span>

First we create a icon.vue component:

<template>
  <span class="icon"></span>
</template>

<script>
export default {
  props: {
    src: { type: String, required: true }
  }
};
</script>

We will load the svg at mounted , where will can just fetch the icon. With the async/await syntax it is quite simple.

async mounted(){
  let svg = await fetch(this.src).then(r => r.text());
  this.$el.innerHTML = svg;
}

But when the component is used multiple times, it will generate multiple request to the same file.

<div v-for="item in list">
  {{item.desp}} 
  <button @click="add(item)">
     <icon src="plus.svg"/>
  </button>
</div>
<!-- it will make as many requests as 
     the list length to plus.svg! -->

So we need to implement some cache. We can do it through Map

let cache = new Map();

async mounted(){
  if(!cache.has(this.src)){
    cache.set(this.src, await fetch(this.src).then(r => t.text()))
  }
  this.$el.innerHTML = cache.get(this.src);
}

Are we good? No. We found that at the first load, it still make multiple requests. The problem is that the cache is not yet populated so subsequent request will still be made. We should store the Promise in cache and let all requests of same src refer to it.

async mounted(){
  if(!cache.has(this.src)){
    cache.set(this.src, fetch(this.src).then(r => t.text()))
  }
  this.$el.innerHTML = await cache.get(this.src);
}

We add some error handling:

async mounted(){
  if(!cache.has(this.src)){
    try{
      cache.set(this.src, fetch(this.src).then(r => t.text()));
    } catch (e) {
      cache.delete(this.src);
    }
  }
  if(cache.has(this.src)){
    this.$el.innerHTML = await cache.get(this.src);
  }
}

The full component code:

<template>
    <span class="icon"></span>
</template>

<script>
let cache = new Map();
export default {
  props: {
    src: { type: String, required: true }
  },
  async mounted() {
    if (!cache.has(this.src)) {
      try {
        cache.set(this.src, fetch(this.src).then(r => r.text()));
      } catch (e) {
        cache.delete(this.src);
      }
    }
    if (cache.has(this.src)) {
      this.$el.innerHTML = await cache.get(this.src);
    }
  }
};
</script>

Remember to also pack your svg files in your webpack config.

*