'Vue 3 Vite - dynamic img src

I'm using Vue 3 with Vite. And I have a problem with dynamic img src after Vite build for production. For static img src there's no problem.

<img src="/src/assets/images/my-image.png" alt="Image" class="logo"/>

It works well in both cases: when running in dev mode and after vite build as well. But I have some image names stored in database loaded dynamically (Menu icons). In that case I have to compose the path like this:

<img :src="'/src/assets/images/' + menuItem.iconSource" />

(menuItem.iconSource contains the name of the image like "my-image.png"). In this case it works when running the app in development mode, but not after production build. When Vite builds the app for the production the paths are changed (all assests are put into _assets folder). Static image sources are processed by Vite build and the paths are changed accordingly but it's not the case for the composed image sources. It simply takes /src/assets/images/ as a constant and doesn't change it (I can see it in network monitor when app throws 404 not found for image /src/assets/images/my-image.png). I tried to find the solution, someone suggests using require() but I'm not sure vite can make use of it.



Solution 1:[1]

Following the Vite documentation you can use the solution mentioned and explained here:

vite documentation

const imgUrl = new URL('./img.png', import.meta.url)

document.getElementById('hero-img').src = imgUrl

I'm using it in a computed property setting the paths dynamically like:

    var imagePath = computed(() => {
      switch (condition.value) {
        case 1:
          const imgUrl = new URL('../assets/1.jpg',
            import.meta.url)
          return imgUrl
          break;
        case 2:
          const imgUrl2 = new URL('../assets/2.jpg',
            import.meta.url)
          return imgUrl2
          break;
        case 3:
          const imgUrl3 = new URL('../assets/3.jpg',
            import.meta.url)
          return imgUrl3
          break;
      }
    });

Works perfectly for me.

Solution 2:[2]

2022 answer: Vite 2.8.6 + Vue 3.2.31

Here is what worked for me for local and production build:

<script setup>
const imageUrl = new URL('./logo.png', import.meta.url).href
</script>

<template>
<img :src="imageUrl" />
</template>

Note that it doesn't work with SSR


Vite docs: new URL

Solution 3:[3]

require() or import() used to solve this scenario in Webpack based Vue apps are indeed Webpack only constructs

Behind the scenes Webpack handles such dynamic imports by making every single file in fixed part of the path (/src/assets/images/) part of the app bundle and available for import. In the case of images, that usually means creating some kind of internal map like this:

{
 'src/assets/images/image1.png' : 'assets/img/image1.hash.png',
  ...
}

The key is the path of the image in the source tree. The value is the path created by the loader responsible for handling images during build (path of image in bundled app). This map is used by Webpack at runtime to "resolve" dynamically generated paths to real paths of images

In Vite there is no require() but you can do something similar by using Glob Import to create a "map" similar to the one described above and the use this map yourself

Or you can use this plugin to do something similar in less transparent way...

More details in this GitHub issue

UPDATE

As Vite is using Rollup behind the scenes, using plugin-dynamic-import-vars might be another solution, with the usage very similar to the Webpack...

BUT this currently have a limitation of only allowing relative paths which makes the Glob Imports probably better choice

Solution 4:[4]

Please try the following methods

const getSrc = (name) => {
    const path = `/static/icon/${name}.svg`;
    const modules = import.meta.globEager("/static/icon/*.svg");
    return modules[path].default;
  };

Solution 5:[5]

Use Vite's API import.meta.glob works well, I refer to steps from docs of webpack-to-vite. It lists some conversion items and error repair methods. It can even convert an old project to a vite project with one click. It’s great, I recommend it!

  1. create a Model to save the imported modules, use async methods to dynamically import the modules and update them to the Model
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
const assets = import.meta.glob('../assets/**')
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    assets: {}
  },
  mutations: {
    setAssets(state, data) {
      state.assets = Object.assign({}, state.assets, data)
    }
  },
  actions: {
    async getAssets({ commit }, url) {
      const getAsset = assets[url]
      if (!getAsset) {
        commit('setAssets', { [url]: ''})
      } else {
        const asset = await getAsset()
        commit('setAssets', { [url]: asset.default })
      }
    }
  }
})
  1. use in .vue SFC
// img1.vue
<template>
  <img :src="$store.state.assets['../assets/images/' + options.src]" />
</template>
<script>
export default {
  name: "img1",
  props: {
    options: Object
  },
  watch: {
    'options.src': {
      handler (val) {
        this.$store.dispatch('getAssets', `../assets/images/${val}`)
      },
      immediate: true,
      deep: true
    }
  }
}
</script>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Roland
Solution 3
Solution 4 kissu
Solution 5 Chieffo