vite: Assets with a dynamic URL are ignored

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 the dist directory with a hash in the filename etc. However this doesn’t happen when the image filename is dynamic. For example:

<ul>
  <li v-for="item in items">
    <img v-bind:src="`icons/${item.slug}.png`" />
    {{ item.name }}
  </li>
</ul>

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.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 13
  • Comments: 24

Commits related to this issue

Most upvoted comments

As mentioned in https://github.com/vitejs/vite/issues/1265#issuecomment-757450141, I tried using glob like const images = import.meta.globEager("/src/images/*.png");. It wasn’t working at first, then I realized that you need to use .default like images["/src/images/image.png"].default.

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. It’s a little bit more work to add each asset in the composable return, but in the long run it’s quite nice,

@SimonSapin

Does Vite support this kind of meta-programming, automatically re-generating generated code when its build script changes?

After vite 2.0.0-beta.17 you can try glob-import to import all resources at once.

As mentioned in #1265 (comment), I tried using glob like const images = import.meta.globEager("/src/images/*.png");. It wasn’t working at first, then I realized that you need to use .default like images["/src/images/image.png"].default.

Just FYI for any one else trying to use globs with static assets.

Did you chose globEager() instead of glob() on purpose? If I understood the Vite docs correctly globEager() imports all assets directly (no lazy-loading). Depending on the number of assets this could have a performance impact.

Here is an example using glob() if you want to lazy-load an asset based on a prop in your component.

<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?

vite 引入图片资源 html: script:

import on from ‘@/assets/voice.png’ import off from ‘@/assets/no_voice.png’

const state = reactive({ voiceSrc: off })