Reputation: 3508
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.
Upvotes: 76
Views: 111427
Reputation: 27779
Solutions for dynamic src binding:
<script setup>
import imageUrl from '@/assets/images/logo.svg' // => or relative path
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
<script setup>
const imageUrl = new URL(`./dir/${name}.png`, import.meta.url).href
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
name: String,
})
const imageUrl = computed(
() => new URL(`@/assets/images/${props.name}.png`, import.meta.url).href
);
</script>
<template>
<img :src="imageUrl" />
</template>
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>
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
Upvotes: 122
Reputation: 3572
If you want to support '@/', you can use next function:
export const loadFile = (file: string) => {
let path = file;
let baseUrl = '';
if (file.startsWith('@/')) {
path = file.replace('@/', '');
}
return new URL(`./${path}`, import.meta.url).href;
}
Working for me for "vite": "^5.2.13"
in latest Vue CLI setup
Why: '@' is not getting resolved in production build but ./ working fine
Upvotes: 0
Reputation: 470
first, check the image directory src/assets/images/
then use import.meta.glob
<template>
<div>
<img :src="imageSrc" alt="Menu Icon" />
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
const menuItem = ref({
iconSource: 'my-image.png'
});
// Use import.meta.glob to load all images from the directory
const images = import.meta.glob('/src/assets/images/*', { eager: true });
const imageSrc = computed(() => {
const imagePath = `/src/assets/images/${menuItem.value.iconSource}`;
if (images[imagePath]) {
return images[imagePath].default;
} else {
return '/src/assets/images/default.png';
}
});
</script>
Upvotes: 3
Reputation: 233
For any newcomers on google, here is the method I used by piecing other answers in this thread together. My use-case is that I wanted to be able to specify a condition to load an image on a component and if that condition wasn't met, to use an alternate image. I tried to use :src and :alt separately, but when a 404 was eventually thrown, the code which was supposed to catch the error never did.
Give this a try if you want to load an image if an image is found, and a different image otherwise:
script:
getImage(mainImage, altImage) {
try {
const modules = import.meta.glob('@/assets/items/*.png', { eager: true, import: 'default' })
const moduleKeys = Object.keys(modules)
const mainImagePath= moduleKeys.find((key) => key.includes(mainImage))
const altImagePath= moduleKeys.find((key) => key.includes(altImage))
return mainImagePath? modules[mainImagePath] : modules[altImagePath]
} catch (err) {
console.error(err)
}
}
...
props: {
mainImage: {
type: String,
default: ""
},
altImage: {
type: String,
default: ""
}
}
and in the template (I am using Vuetify):
<v-card class="pa-2">
<v-row>
<v-col>
<v-avatar color="white">
<v-img
:src="getImage(mainImage, altImage)"
>
</v-img>
</v-avatar>
</v-col>
<v-btn size="x-small" icon>
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-col>
</v-row>
</v-card>
Upvotes: 1
Reputation: 11
<template>
<img :src="getImageUrl(imageName)" alt="Avatar" class="size-10" />
</template>
<script setup>
import { ref } from "vue";
const imageName = ref('df0e160d30bdbf8e0706c92ebf148036.png');
function getImageUrl(name) {
return new URL(`../../assets/${name}`, import.meta.url).href;
}
</script>
<style scoped>
.size-10 {
width: 10rem;
height: 10rem;
}
</style>
Upvotes: 1
Reputation: 20832
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, import: 'default' })
const moduleKeys = Object.keys(modules)
const fileSrc = moduleKeys.find(key => key.endsWith(fileName))
return fileSrc ? modules[fileSrc] : ''
} catch (err) {
console.log(err)
}
}
<img :src="getImage('whatever.png')" />
Upvotes: 5
Reputation: 21
This worked for me
<img v-for="image in project.images" :key="image.index" :src="`${getImagePath(image.src)}`" :alt="image.alt" />
getImagePath(image) {
const imgUrl = `/nestedPublicFolder/${image}.jpg`;
return imgUrl;
},
Upvotes: 0
Reputation: 582
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.
Upvotes: 11
Reputation: 391
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)
Upvotes: 1
Reputation: 17
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']
}
Upvotes: 1
Reputation: 111
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="...">
Upvotes: 6
Reputation:
Following the Vite documentation you can use the solution mentioned and explained here:
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.
Upvotes: 21
Reputation: 469
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.
Upvotes: 19
Reputation: 7
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.
Upvotes: -3
Reputation: 18428
My enviroment:
vite
v2.9.13vue3
v3.2.37In vite.config.js
, assign @assets
to src/assets
'@assets': resolve(__dirname, 'src/assets')
<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>
require
solution.require
variable" error from browser. So the answer with require
not working for me.nodejs >= 14
no longer has require
by default. See this thread. I tried the method, but my Vue3 + vite
give me errors.Upvotes: 1
Reputation: 1346
In the context of [email protected]
, 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 [email protected]
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'
}
})
Upvotes: 1
Reputation: 27
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;
};
Upvotes: 1
Reputation: 39
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!
// 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 })
}
}
}
})
.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>
Upvotes: 0