54

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.

kissu
  • 40,416
  • 14
  • 65
  • 133
Ondrej Vencovsky
  • 3,188
  • 9
  • 28
  • 34

13 Answers13

78

Update 2022: Vite 3.0.9 + Vue 3.2.38

Solutions for dynamic src binding:

1. With static URL

<script setup>
import imageUrl from '@/assets/images/logo.svg' // => or relative path
</script>

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

2. With dynamic URL & relative path

<script setup>
const imageUrl = new URL(`./dir/${name}.png`, import.meta.url).href
</script>

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

3.With dynamic URL & absolute path

Due to Rollup Limitations, all imports must start relative to the importing file and should not start with a variable.

You have to replace the alias @/ with /src

<script setup>
const imageUrl = new URL('/src/assets/images/logo.svg', import.meta.url)
</script>

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


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

Roland
  • 24,554
  • 4
  • 99
  • 97
  • 29
    How can I use it with path like `@/assets/some_image.jpg`? – jr6tp Mar 27 '22 at 23:42
  • 1
    "However, the URL string must be static [...]" - which means it will not work for this question, which is explicitly about dynamic urls. – Zaz Jul 06 '22 at 19:00
  • @Zaz Dynamic urls are also supported. From the docs: `new URL(`./dir/${name}.png`, import.meta.url).href` – Roland Jul 07 '22 at 08:00
  • @jr6tp you should be able to use it with a direct import like `import url from '@/assets/some_image.jpg'` and then use `url` in the `:src` attribute, no need for `new URL()`. – TheGreenkey Aug 06 '22 at 12:54
  • @jr6tp updated the answer. – Roland Sep 05 '22 at 17:07
  • 19
    This is more difficult than it should be. – Artur Müller Romanov Nov 03 '22 at 17:28
  • const imgUrl = props.icon ? new URL('/resources/images/'+props.icon, import.meta.url).href : false; tried this with latest Laravel + Vue, and at production I get non-hashed file name – Shaine Eugene Mednikov Nov 25 '22 at 09:18
  • 1
    @Roland "Dynamic URL" in this case means a variable used in a string literal, not a string constructed at runtime. The Vite docs states that the URL string must be static: https://vitejs.dev/guide/assets.html#new-url-url-import-meta-url "However, the URL string must be static so it can be analyzed, otherwise the code will be left as is..." So your proposed solution for dynamic URLs doesn't work. – Daniel T. Jan 06 '23 at 20:31
  • I didn't see the new URL in the docs and had to handle promises with import() because of that. This saved me a lot of time, thanks! – antoni May 24 '23 at 16:17
  • @Roland Should definitely add the glob option for absolute paths. https://vitejs.dev/guide/features.html#glob-import – hitautodestruct May 30 '23 at 17:54
18

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.

kissu
  • 40,416
  • 14
  • 65
  • 133
9

The simplest solution I've found for this is to put your images in the public folder located in your directory's root.

You can, for example, create an images folder inside the public folder, and then bind your images dynamically like this:

<template>
  <img src:="`/images/${ dynamicImageName }.jpeg`"/>
</template>

Now your images should load correctly in both development and production.

M2K
  • 319
  • 3
  • 11
5

According to the docs here. For dynamic images, it's best to do something like this...

/* create a util function or method or computed property
   also assuming your static images are in assets folder
*/
const getImageUrl = (path: string) => {
 return new URL(`../assets/${path}`, import.meta.url).href;
};

// and use in code like this assuming products is a list
<ul>
  <li v-for="product in products" :key="product.id">
   <img alt="nice image" :src="getImageUrl(product.img)" />
  </li>
</ul>

This works for me perfectly and I hope it helps someone.

Samson Iyanda
  • 512
  • 3
  • 13
4

All you need is to just create a function which allows you to generate a url.

from vite documentation static asset handling

const getImgUrl = (imageNameWithExtension)=> new URL(`./assets/${imageNameWithExtension}`, import.meta.url).href;

//use

<img :src="getImgUrl(image)" alt="...">
s17
  • 71
  • 5
2

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;
  };
kissu
  • 40,416
  • 14
  • 65
  • 133
Renato
  • 29
  • 3
  • 3
    Code-only answers are not particularly helpful. Please add some descriptions of how this code solves the problem. – Al Foиce ѫ Jun 17 '21 at 08:32
  • Hi. Well, this works. But how does it work? Can you explain? The code loading all icons runs each time an icon is requested, or not? It would be very inefficient, wouldn't it? – Ondrej Vencovsky Jun 22 '21 at 14:10
  • 1
    This is not a very good method, and there is indeed a problem of introducing too much useless content, but I tried to use a variable to define the image path to `import.meta.globEager`, this method will report an error, it can only receive a string type. If there is a better solution that can be provided to me, thank you very much! – Renato Jun 24 '21 at 05:22
1

In the context of vite@2.x, you can use new URL(url, import.meta.url) to construct dynamic paths. This pattern also supports dynamic URLs via template literals. For example:

<img :src="`/src/assets/images/${menuItem.iconSource}`" />

However you need to make sure your build.target support import.meta.url. According to Vite documentation, import.meta is a es2020 feature but vite@2.x use es2019 as default target. You need to set esbuild target in your vite.config.js:

  // vite.config.js
  export default defineConfig({
    // ...other configs

    optimizeDeps: {
      esbuildOptions: {
        target: 'es2020'
      }
    },

    build: {
      target: 'es2020'
    }
  })
KienHT
  • 1,098
  • 7
  • 11
1

The other examples worked for me. An alternative however is to import it like this:

<script setup>
  ...other code

  onMounted(() => {
    getImageUrl()
  })

  const url = ref()
  const getImageUrl = async () => {       
    url.value = (await import(/* @vite-ignore */`../assets/images/${dynamicValue.value}.png`)).default
  }
</script>
<template>
  <img :src="url">
</template>

(vue3/vite4/node18)

Per
  • 332
  • 3
  • 18
0

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>
Chieffo
  • 39
  • 6
0

My enviroment:

  • vite v2.9.13
  • vue3 v3.2.37

In vite.config.js, assign @assets to src/assets

'@assets': resolve(__dirname, 'src/assets')

Example codes:

<template>
    <div class="hstack gap-3 mx-auto">
        <div class="form-check border" v-for="p in options" :key="p">
            <div class="vstack gap-1">
                <input class="form-check-input" type="radio" name="example" v-model="selected">
                <img :src="imgUrl(p)" width="53" height="53" alt="">
            </div>
        </div>
    </div>
</template>

<script>

import s1_0 from "@assets/pic1_sel.png";
import s1_1 from "@assets/pic1_normal.png";

import s2_0 from "@assets/pic2_sel.png";
import s2_1 from "@assets/pic2_normal.png";

import s3_0 from "@assets/pic3_sel.png";
import s3_1 from "@assets/pic3_normal.png";

export default {
    props: {
        'options': {
            type: Object,
            default: [1, 2, 3, 4]
        }
    },
    data() {
        return {
            selected: null
        }
    },
    methods: {
        isSelected(val) {
            return val === this.selected;
        },
        imgUrl(val) {
            let isSel = this.isSelected(val);
            switch(val) {
                case 1:
                case 2:
                    return (isSel ? s1_0 : s1_1);

                case 3:    
                case 4:
                    return (isSel ? s2_0 : s2_1);
                    
                default:
                    return (isSel ? s3_0 : s3_1);
            }
        }
    }
}
</script>

References:

enter image description here

Memo:

About require solution.

  • "Cannot find require variable" error from browser. So the answer with require not working for me.
  • It seems nodejs >= 14 no longer has require by default. See this thread. I tried the method, but my Vue3 + vite give me errors.
AechoLiu
  • 17,522
  • 9
  • 100
  • 118
0

In Nuxt3 I made a composable that is able to be called upon to import dynamic images across my app. I expect you can use this code within a Vue component and get the desired effect.

const pngFiles = import.meta.glob('~/assets/**/*.png', {
  //@ts-ignore
  eager: true,
  import: 'default',
})

export const usePNG = (path: string): string => {
  // @ts-expect-error: wrong type info
  return pngFiles['/assets/' + path + '.png']
}

sources

antokhio
  • 1,497
  • 2
  • 11
  • 16
0

Use import.meta.glob for absolute paths with variables

For absolute paths, there is a way to load images dynamically. It even supports the @ alias.

Try import.meta.glob

Heres a little convenience method for getting a file.

function getImage(fileName) {
  try {
    const modules = import.meta.glob('@/assets/images/**/*.{png,svg}', { eager: true })
    const moduleKeys = Object.keys(modules)
    const fileSrc = moduleKeys.find(key => key.includes(fileName))

    return fileSrc ? modules[fileSrc].default : ''
  } catch (err) {
    console.log(err)
  }
}

const imageSrc = ref('')
imageSrc.value = getImage(`${whatever}.png`)
<img :src="imageSrc" />
hitautodestruct
  • 20,081
  • 13
  • 69
  • 93
-3

If you have a limited number of images to use, you could import all of them like this into your component. You could then switch them based on a prop to the component. enter image description here

Importing images

Emmy Leke
  • 7
  • 2