Dony
Dony

Reputation: 1979

Vite / Vue 3 : "require is not defined" when using image source as props

I switched from the Vue CLI to Vite CLI, and from the Composition API of Vue 3 to SFC Script setup API.

How it previously worked for me

When I was using the official Vue CLI, I could import an image source by passing the filename of the path by the props :

<template>
  <img :src="require(`@/assets/${imagePath}`)"/>
<template/>

<script>
export default {
  props: {
    imagePath: { type: String },
  },
  setup() {
    // ...
  }
}
<script/>

And then call it like this :

<template>
  <Image imagePath="icon.png" />
</template>

The error I get since I migrated to Vite

But since I migrated to the Vite CLI, I have an error "Uncaught ReferenceError: require is not defined". My file now use the script setup syntax and looks like this :

<script setup>
const props = defineProps({
  imagePath: { type: String },
})
</script>

<template>
  <img :src="require(`@/assets/${props.imagePath}`)"/>
</template>

require is not define error

What I tried

I already tried to import the file directly from the assets folder with a relative path, and it worked. But I cannot specify the path from props with the import statement.

<script setup>
// Works but do not use the props, so the component is not reusable
import logo from "./../assets/logo.png"
</script>

<template>
  <img :src="logo"/>
</template>
<script setup>
// Component is reusable but the import statement has illegal argument I guess
const props = defineProps({
  imagePath: { type: String },
})

import logo from `./../assets/${props.imagePath}`
</script>

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

I also tried the import statement in the template but it cannot even compile the code :

<script setup>
const props = defineProps({
  imagePath: { type: String },
})
</script>

<template>
  <img :src="import `./../assets/${props.iconPath}`" />
</template>

Am I missing something ? Maybe a plugin exists and can help me achieve this ?

Upvotes: 50

Views: 79790

Answers (8)

d9k
d9k

Reputation: 2044

Was able to make vite/webpack-agnostic solution with vite-plugin-transform by Silksofthesoul

vite.config.ts:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import transformPlugin from 'vite-plugin-transform';

export default defineConfig({
  plugins: [
    vue(),
    transformPlugin({
      callbackArray: [str => str.replace(/__VITE_ONLY_ASSIGN__/gim, '')],
    }),
  ],
  define: {
    'process.env': {},
  },
});

In file with non-working require():

const __VITE_ONLY_ASSIGN__require = (relativePath: string) => {
  return new URL(relativePath, import.meta.url).href;
};

const myImageUrl = require('./my-image.svg');

Upvotes: 0

chimpsociety
chimpsociety

Reputation: 81

I am currently struggeling with the same thing, with the added complication that the app will be deployed to k8s, where the ingress routing adds another level of absurdity for file paths.

I have tried all suggestions here, nothing worked well for both development and prod.

In the end, I simply kept the pictures I load dynamically in the public folder. Per Vue's own website, this folder is copied but does not go through webpack, so paths remain intact.

It's not super elegant, but it works incredibly well. This finally solved the headache I have been having for weeks over multiple web apps.

Upvotes: 1

kuvasney
kuvasney

Reputation: 11

Just had the same issue. I have a global function that gets the image path depending on product name. That's how I made it work.

Many thanks to @leipzy

export const getProductLogo = (cart) => {

  
  if (cart) {
    let im
    try {
      im = `../assets/img/cart/${cart}.png`
    } catch (err) {
      im = '../assets/img/default-logo.svg'
    }
    return new URL(`${im}`, import.meta.url).href
  }
}

Upvotes: 1

savhascelik
savhascelik

Reputation: 59

for those who will use static assets with vue + vite, they can use this method.

import imgUrl from '../../img/bgimg.jpg'

export default {
  data() {
    return {
      bgImage: imgUrl
    };
  },

};

then you can use it like this

<div class="ms-auto h-100 z-index-0 ms-n6"
    :style="{
    backgroundImage:
    'url(' +
    bgImage +
    ')',
    }"
></div>

Importing a static asset will return the resolved public URL when it is served:

import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl

For example, imgUrl will be /img.png during development, and become /assets/img.2d8efhg.png in the production build.

see also: https://vitejs.dev/guide/assets.html#importing-asset-as-url

Upvotes: 4

Shatlyk
Shatlyk

Reputation: 1

If none of provided answers is not help (as in my case) - try to use <slot/> instead, as shown below:

//card component (child)
<template>
  <div class="card">
    <slot />
  </div>
</template>

//services component (parent)
<template>
  <ServiceCard>
    <img src="@/assets/icons/service-1.svg" alt="svg image" />
  </ServiceCard>

  <ServiceCard>
    <img src="@/assets/icons/service-2.svg" alt="svg image" />
  </ServiceCard>
</template>

Upvotes: 0

In case you're using require.context, the new way seems to be glob import. Change your old statement from:

const locales = require.context("../../lang", true, /[A-Za-z0-9-_,\s]+\.json$/i)

to:

const locales = import.meta.glob('../../lang/*.json')

Edit:

This also seems to replace require.

Upvotes: 14

leipzy
leipzy

Reputation: 12754

I also encountered this problem. I searched about this and found according to this github issue comment,

There should never be require in source code when using Vite. It's ESM only.

More about this on Features | Vite - Static Assets

A bit of searching lead me to this Vue 3 code sample link that worked for me

<CarouselItem v-for="(item,index) of carouselData" :key="index">
 <img :src="getImageUrl(item.img_name)" />
</CarouselItem>
setup() {
  const getImageUrl = (name) => {
        return new URL(`../../lib/Carousel/assets/${name}`, import.meta.url).href
    }
  return { carouselData, getImageUrl }
}

Upvotes: 61

tony19
tony19

Reputation: 138226

Use a watcher on the imagePath prop that dynamically imports the image, and updates a ref (bound to the <img>.src) with the result:

<script setup>
import { ref, watchEffect } from 'vue'

const props = defineProps({
  imagePath: { type: String },
})

const logo = ref()
watchEffect(async () => {
  logo.value = (await import(/* @vite-ignore */ `../assets/${props.imagePath}`)).default
})
</script>

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

demo

Upvotes: 1

Related Questions