-
-
Notifications
You must be signed in to change notification settings - Fork 6.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assets with a dynamic URL are ignored #1265
Comments
I'd say this is not a problem of vite. You cannot achieve same behavior even in other bundlers/scaffolders. As you said, if the value of
Or if images are too many to be imported explicitly, you may want to use rollup-plugin-dynamic-import-vars |
Repeating the name of each image three times in a module that does nothing but reexport gets verbose quickly, and too easy to become out of date when there are a few hundred images. https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars looks relevant, thanks! (Although the concept of "importing" PNG images into JS modules feels… unusual.) |
And for the template context to refer to imported images' url, you need to bind that urls to vue component instance once (either using
Nah, it's not importing images into JS modules, it's importing image url as a string into JS modules. What happens behind the scene is basically:
So if you write some code like |
This helps understand how everything ties together, thanks. Given that rollup needs to know about all URLs it is to manage it makes sense that URLs cannot be truly dynamic, for example based on user input. What I’m looking for is URLs that are dynamic as far as human-authored code (JS modules, Vue components, …) is concerned, but known at "project build" time. In my case they are derived from identifier in a (static) JSON file. If import statements in JS source files were the only way to make rollup include a PNG file in the build I’d consider making a script that parses my JSON file and generates a JS file like you suggested: import foo from 'xxx/foo.png'
import bar from 'xxx/bar.png'
// Dozens more…
export default {
foo,
bar,
// Dozens more…
} Does Vite support this kind of meta-programming, automatically re-generating generated code when its build script changes? But since rollups has plugins I assume imports are not the only way and I could instead configure By the way, does Vite use rollup at all in dev mode? |
Yes, partially. You can write a plugin to resolve your image requests as an in-memory virtual JS module (they dont even need to be emitted to the file system). However, you still need an import statement some where in your code to "trigger" your plugin to do that.
Yes |
The images are already on the filesystem, I just need them copied to If a plugin triggered by a single JS import can then programmatically emit many "requests" during build mode, that sounds like what I want. I look into it further, thanks! Feel free to close as "scenario can (probably) be supported by a custom plugin", or leave this open as a feature request. This seems to me like a not-so uncommon pattern that could potentially be supported directly by Vite, but I’m not sure yet what that feature would look like. |
Yes, if copying static assets to output directory is the only thing you want.
I'm not really sure about what the missing feature is actually. If you want to import some images programmatically (of course from a fixed set), it is supported already, but you need to some how write the code to import them (that's ... programmatically); If you want to have an array or an object holding all the hashed urls to refer to certain asset by code (like BTW, I'm not from the team so cannot close issues :) |
After |
@SimonSapin I wrote a plugin to help transition a few medium sized vue2/webpack projects to vue3/vite that solves a similar problem, though I didn't have your exact use case in mind. It's probably not a good long term solution, but was convenient for quickly converting our old
which imports and exposes e.g. |
As mentioned in #1265 (comment), I tried using Just FYI for any one else trying to use globs with static assets. |
Based on @amir20 I ended up doing something like this: export default function useAssets() {
const svgs = import.meta.globEager('/src/assets/*.svg');
const pngs = import.meta.globEager('/src/assets/*.png');
const jpegs = import.meta.globEager('/src/assets/*.jpeg');
return {
aboutImage: svgs['/src/assets/aboutImage.svg'].default,
search: svgs['/src/assets/search.svg'].default,
info: pngs['/src/assets/info.png'].default,
};
} Then in any file: <template>
<div>
<img :src="assets.info">
</div>
</template>
<script lang="ts">
import { defineComponent } from '@vue/runtime-core';
import useAssets from '../composable/useAssets';
export default defineComponent({
setup() {
const assets = useAssets();
return {
assets,
};
},
});
</script> This way you can keep all your assets in one place and avoid magic strings. |
…c instead See vitejs/vite#1265 The discussion provides some workaround, but author couldn't figure it out properly.
Did you chose Here is an example using <template>
<img :src="chosenImageSource" />
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
export default defineComponent({
props: {
slug: {
type: String,
required: true,
},
},
setup(props) {
const allImages = import.meta.glob("../assets/img/*.jpg");
let chosenImageSource = ref("");
onMounted(() => {
allImages[`../assets/img/${props.slug}.jpg`]().then((mod) => {
chosenImageSource.value = mod.default;
});
});
return { chosenImageSource };
},
});
</script> Would this be a good way to handle it or is there a more simple or straightforward way of solving this? |
Using From the example, my Image component ended up looking like this: <script setup>
import { ref, watchEffect } from 'vue'
const props = defineProps({
path: { type: String },
alt: { type: String },
css: { type: String },
})
const image = ref()
watchEffect(async () => {
image.value = (await import(/* @vite-ignore */ `../assets/${props.path}.jpg`)).default
})
</script>
<template>
<img :src="image" :alt="alt" :class="css" />
</template> And then using it this way: <div v-for="series in leadership" :key="series.title" class="flex flex-col gap-6 bg-sky-50 rounded-lg border-4 border-sky-500">
<Image :path="series.banner" :alt="`${series.title} Banner`" css="hidden sm:block rounded-xl w-full h-auto px-4" />
</div> Probably the only caveat was hard-coding the type of image, in this case jpg. The build command wouldn't continue if that part wasn't static. [plugin:rollup-plugin-dynamic-import-variables] invalid import "import(/* @vite-ignore */ `../assets/${props.path}`)". A file extension must be included in the static part of the import. For example: import(`./foo/${bar}.js`). From the error I also understood that, as @nandin-borjigin commented, using rollup-plugin-dynamic-import-vars was the way to go. I didn't have to add it, it was already included in Thanks, everyone, for the help in figuring this out. 💪💪 |
😱 It really does seem like something blew up. 🤔 I deployed a test on
netlify, and it worked correctly without any errors. Mind sharing some of
the code you used? For example, the image component and how you ended up
dynamically providing the image paths for the component. 👍
…On Wed, Feb 9, 2022 at 2:47 PM utitofon ***@***.***> wrote:
This works perfectly fine during local development but after deploying to
a service like netlify, I get errors like
[image: Screenshot_9]
<https://user-images.githubusercontent.com/69417647/153287223-9904d19b-bee4-46d5-89d4-36cc4b65293d.png>
—
Reply to this email directly, view it on GitHub
<#1265 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFRZJOTZ47K5M5JDHTI2KDU2LHHDANCNFSM4VKGONTQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you commented.Message ID:
***@***.***>
--
~Victor A. Lacayo
|
Any idea what's wrong? |
After testing a few things, I believe the problem has to do with the path you used: "companies/catalog". You have to use the filename alone. "Companies" would have to be hard-coded. Obviously, this is already a hacky method of doing things, so I haven't thought up a more elegant solution. You can solve this in two different ways: (1) Create an image component for just images in your catalog folder (i.e. Here's an image component, like I mentioned in the second solution. As well as a few updates that I did to my own image component. //Two-levels Image Component
<script setup>
const props = defineProps({
path: { type: String },
alt: { type: String },
})
const fileType = props.path.match(/\.[0-9a-z]+$/i)
const cleanPath = props.path.replace(fileType,'')
const pathArray = cleanPath.split('/')
const folderPath = pathArray[pathArray.length - 2]
const imagePath = pathArray[pathArray.length - 1]
const image = ref()
watchEffect(async () => {
switch (fileType[0]) {
case '.jpg':
image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.jpg`)).default
break;
case '.jpeg':
image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.jpeg`)).default
break;
case '.png':
image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.png`)).default
break;
case '.svg':
image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.svg`)).default
break;
default:
console.log(`Sorry, the image component can't recognize the ${fileType} file type just yet.`);
}
})
</script>
<template>
<img :src="image" :alt="alt" />
</template> A few things that differ in this new component from the old one:
|
I'm getting a filtype is null error
[image: Screenshot_3.png]
…On Thu, Feb 10, 2022 at 11:36 PM Victor A. Lacayo ***@***.***> wrote:
After testing a few things, I believe the problem has to do with the path
you used: "companies/catalog". You have to use the filename alone.
"Companies" would have to be hard-coded. Obviously, this is already a hacky
method of doing things, so I haven't thought up a more elegant solution.
You can solve this in two different ways: (1) Create an image component for
just images in your catalog folder (i.e. image.value = (await import(/*
@vite-ignore */ `../assets/images/companies/${props.path}.jpg`)).default)
or (2) create a component that *assumes* there will be one level down
directory as well.
Here's an image component, like I mentioned in the second solution. As
well as a few updates that I did to my own image component.
//Two-levels Image Component<script setup>const props = defineProps({ path: { type: String }, alt: { type: String },})const fileType = props.path.match(/\.[0-9a-z]+$/i)const cleanPath = props.path.replace(fileType,'')const pathArray = cleanPath.split('/')const folderPath = pathArray[0]const imagePath = pathArray[1]const image = ref()watchEffect(async () => { switch (fileType[0]) { case '.jpg': image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.jpg`)).default break; case '.jpeg': image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.jpeg`)).default break; case '.png': image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.png`)).default break; case '.svg': image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.svg`)).default break; default: console.log(`Sorry, the image component can't recognize the ${fileType} file type just yet.`); }})</script>
<template>
<img :src="image" :alt="alt" />
</template>
A few things you can notice from this new component from the old one:
1. You can now include the file type. It will be automatically removed
from the path and then used to decide which import statement to use.
Not super elegant because it has much repetition, but it's the best I have
come up with so far to avoid making several image components.
2. I've deleted the CSS props simply because you can use class on the
Vue template anyway, so it was unnecessary.
3. This template assumes there are two levels. So just keep that in
mind.
—
Reply to this email directly, view it on GitHub
<#1265 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AQRTVLZB2INDSYLQEP2KBXDU2Q4YPANCNFSM4VKGONTQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you commented.Message ID:
***@***.***>
|
It works now forgot to add the file type 😅😅 |
It's been solved, thank you.
…On Thu, Feb 10, 2022 at 11:36 PM Victor A. Lacayo ***@***.***> wrote:
After testing a few things, I believe the problem has to do with the path
you used: "companies/catalog". You have to use the filename alone.
"Companies" would have to be hard-coded. Obviously, this is already a hacky
method of doing things, so I haven't thought up a more elegant solution.
You can solve this in two different ways: (1) Create an image component for
just images in your catalog folder (i.e. image.value = (await import(/*
@vite-ignore */ `../assets/images/companies/${props.path}.jpg`)).default)
or (2) create a component that *assumes* there will be one level down
directory as well.
Here's an image component, like I mentioned in the second solution. As
well as a few updates that I did to my own image component.
//Two-levels Image Component<script setup>const props = defineProps({ path: { type: String }, alt: { type: String },})const fileType = props.path.match(/\.[0-9a-z]+$/i)const cleanPath = props.path.replace(fileType,'')const pathArray = cleanPath.split('/')const folderPath = pathArray[0]const imagePath = pathArray[1]const image = ref()watchEffect(async () => { switch (fileType[0]) { case '.jpg': image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.jpg`)).default break; case '.jpeg': image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.jpeg`)).default break; case '.png': image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.png`)).default break; case '.svg': image.value = (await import(/* @vite-ignore */ `../assets/images/${folderPath}/${imagePath}.svg`)).default break; default: console.log(`Sorry, the image component can't recognize the ${fileType} file type just yet.`); }})</script>
<template>
<img :src="image" :alt="alt" />
</template>
A few things you can notice from this new component from the old one:
1. You can now include the file type. It will be automatically removed
from the path and then used to decide which import statement to use.
Not super elegant because it has much repetition, but it's the best I have
come up with so far to avoid making several image components.
2. I've deleted the CSS props simply because you can use class on the
Vue template anyway, so it was unnecessary.
3. This template assumes there are two levels. So just keep that in
mind.
—
Reply to this email directly, view it on GitHub
<#1265 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AQRTVLZB2INDSYLQEP2KBXDU2Q4YPANCNFSM4VKGONTQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you commented.Message ID:
***@***.***>
|
thanks man |
Is your feature request related to a problem? Please describe.
When something like
<img src=example.png>
is used in a Vue template,vite build
handles it by copying the PNG file into thedist
directory with a hash in the filename etc. However this doesn’t happen when the image filename is dynamic. For example:The
src
attribute in the browser’s DOM are exactly the result of template interpolation, which works out with Vite’s own development server but not in "production" since image files are missing.Describe the solution you'd like
I sort of understand why this doesn’t Just Work, but it’d be nice if it did. Alternatively, is there some other ways to tell Vite about which images exist? The value of
item.slug
is always in some finite set, although there are more of them that I’d rather hard-code in a template. Or, am I doing something very wrong and shouldn’t use reactive data for this? I’m very new to Vue.Describe alternatives you've considered
Moving these images to the
public
directory would probably work, but Vite’s README describes this as an escape hatch that is best avoided.The text was updated successfully, but these errors were encountered: