Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

andyjameswhite's avatar

Vue Import Variable in Path Name Fails - Jetsream + Vue

Trying to setup a generic component to import Icons (in this example HeroIcon).

Vue/Vite does not seem to like variables in the path name.

Steps to reproduce

  1. Fresh install of Jetstream with Vue+Inertia
  2. Install Heroicons
  3. Create a component in the Components directory, e.g. HeroIcon.vue
<script setup>
import { defineAsyncComponent } from 'vue'

//This works
const HeroIcon = defineAsyncComponent(() =>
  import(`@heroicons/vue/24/solid/SunIcon.js`)
);

//This doesn't work
const filename = 'SunIcon';
const HeroIcon = defineAsyncComponent(() =>
	import(`@heroicons/vue/24/solid/${filename}.js`)
);
</script>

<template>
	<HeroIcon />
</template>
  1. In the Dashboard.vue file import the Component and add the tag inside the template
import HeroIcon from '@/Components/HeroIcon.vue';

<HeroIcon /> 

I've looked around and other people have had similar issues, but after hours of searching I cannot find an answer which works for me.

The error reported on the console is the following

Uncaught (in promise) TypeError: The specifier “@heroicons/vue/24/solid/SunIcon.js” was a bare specifier, but was not remapped to anything. Relative module specifiers must start with “./”, “../” or “/”.

Obviously this is indicating that it has a problem with the path, but I don't think it is down to anything to do with the actual path, considering the hard coded path to the same location works fine.

I'm assuming it is perhaps something to do with order in which Vite compiles things and that it cannot evaluate the path properly when there is a variable included.

Obviously the above is a simplified version, once this was working I was then going to

  1. Pass a "variant" or "name" prop so the tag in page can dictate the icon to load

  2. include a reference to the HeroIcon component in the app.js file to make the component available across my application. (I'm very much learning but this seemed like an ok approach considering the async nature inside the Heroicon component.)

The general problem I'm trying to solve here is...

I really like the way Vue works and can see the power of the way things are broken down into the components, but it seems to me like an overhead I can do without to have to import every icon on every page, each with it's own tag.

I tend to use multiple items on different pages, any page might have 5 to 10 icons, with perhaps 50 to 100 across the app. This was my attempt to keep my code a bit slimmer and reusable.

Perhaps there is a better solution to achieve what I am trying to do - or I'm missing something obvious - help is much appreciated.

1 like
5 replies
LaryAI's avatar
Level 58

The issue you're encountering is related to how Vite handles dynamic imports. When using dynamic imports with variables, Vite needs to be able to resolve the path at build time, which can be problematic with variable paths. However, there are ways to work around this limitation.

Here's a solution that involves creating a mapping of your icons and using that to dynamically import them:

  1. Create a mapping of your icons: You can create a JavaScript object that maps icon names to their respective import paths.

  2. Use the mapping to dynamically import the icons: Use the mapping to resolve the correct path for the icon you want to import.

Here's how you can implement this:

  1. Use the component in your application: Now you can use the HeroIcon component and pass the icon name as a prop.
<!-- Dashboard.vue -->
<template>
  <HeroIcon name="SunIcon" />
</template>

<script>
import HeroIcon from '@/Components/HeroIcon.vue';

export default {
  components: {
    HeroIcon,
  },
};
</script>

Explanation:

  • Icon Mapping: The iconMap object maps icon names to their respective import functions. This allows you to dynamically import the correct icon based on the name provided.

  • Dynamic Component: The <component :is="iconComponent" /> tag in the template dynamically renders the component that iconComponent resolves to.

  • Error Handling: If an icon name is not found in the iconMap, an error is logged to the console.

This approach allows you to keep your code DRY and manage icons more efficiently across your application. You can expand the iconMap with more icons as needed.

andyjameswhite's avatar

@LaryAI This works pretty well

Moved a couple of bits around and used shallowRef instead of ref to resolve a console warning

andyjameswhite's avatar

This solution is near enough what I thought I would have to do, and seems to be a pretty good way to go, all the icons are at least listed in just one place.

I'd be interested to know what anyone's opinion was of the likely performance of this type of solution.

I'm too new to Vite/Vue to know where to start analysing it, so some tips on how to do that would be greatly appreciated.

Mega_Aleksandar's avatar

As Lary said, Vite needs to know the actual filename before it builds. So either a collection of some sort import.glob for example and then you check the variable filename against that collection. You can see the import.glob in action here https://inertiajs.com/client-side-setup

resolve: name => {
    const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
    return pages[`./Pages/${name}.vue`]
  },

Hope it clears things up.

1 like
andyjameswhite's avatar

@Mega_Aleksandar I kind of follow

My icon Component is in ./Components not ./Pages so I moved it to see if it would get picked up but it still didn't work, same error as my original post

Are you suggesting my original code would be possible to make work by including it within the resolve section above?

My app.js file looks a bit different to yours, like so


createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, App, props, plugin }) {
        return createApp({ render: () => h(App, props) })
            .use(plugin)
            .component('Link', Link)
            .use(ZiggyVue)
            .mount(el);
    },
    progress: {
        color: '#4B5563',
    },
});

Please or to participate in this conversation.