Recently I've converted a Vue project that was using Webpack to Vite. All went smooth until I've discovered that Vite doesn't support require()
out of the box.
I'm using some custom SVG icons that I'm keeping in a folder call obviously svg
. With Webpack it was easy to import them and I had several alternatives on how to do it.
To load them I was using vue-inline-svg plus a custom Vue component that wraps around this package:
<template>
<InlineSvg
:src="require(`@/svg/${name}.svg`)"
class="fill-current"
/>
</template>
<script>
import InlineSvg from "vue-inline-svg";
export default {
components: { InlineSvg },
props: {
name: {
type: String,
required: true,
},
},
};
</script>
This is just a simple custom Vue component that accepts a prop
called name
and loads <InlineSvg />
under the hood.
As a side note, my custom component called icon
is defined as a global component inside the app.js
file, so I can use it all over the place:
import Icon from "@/components/Icon.vue";
const app = createApp({ render: () => h(App, props) })
.component("icon", Icon);
Usage:
<icon name="some-icon-name" />
This approach was simple, but unfortunately it's NOT working out of the box with Vite, so let's fix it.
Solution 1: Adding back require()
Probably the easiest way to fix this is to use vite-plugin-require which adds support for require()
to Vite.
You should not see any breaking changes while using this and I believe it's the most elegant way to do it if you're already using require()
to load your SVGs.
Update: I've noticed a bug while using this method in Vite 3.x
Solution 2: Importing SVGs as components
This is what I personally use and I think it's the most elegant way of doing it.
If you are not using any SVG loader or/and a custom Vue component as a wrapper, you can use vite-svg-loader which allows you to tap straight into the SVG and import it as a Vue component.
More than that you can go a step forward and a custom wrapper over it to achieve something similar with the other 2 solutions, by making use of dynamic components.
To set it up you first need to install it:
npm i vite-svg-loader
Next you need to add this plugin to your vite.config.js
:
import svgLoader from 'vite-svg-loader';
export default defineConfig({
plugins: [
// ...
svgLoader(),
],
});
Now you can import each SVG as component, url or raw... but importing each SVG individually will make a huge mess into our project.
To fix this issue, we can create a component that will dynamically load the SVG we want:
<script setup>
import { defineAsyncComponent } from 'vue';
const props = defineProps({
name: {
type: String,
required: true,
},
});
const icon = defineAsyncComponent(() =>
import(`../assets/svg/${props.name}.svg`)
);
</script>
<template>
<component :is="icon" class="fill-current" />
</template>
Now you can use this new component by importing it whenever you need it in your project or add it as a global into your app.js
.
<icon name="user" />
Solution 3: Adding your SVGs as Vue components
While I do not recommend this, it might be helpful if you have just a few icons you need to include and reuse here and there. This is more or less a brainless approach, but it works if you're lazy.
So what you can do is to define your SVGs into separate Vue components. Example this Check.vue
placed inside the icons
folder:
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
class="fill-current"
>
<path
d="M 28.28125 6.28125 L 11 23.5625 L 3.71875 16.28125 L 2.28125 17.71875 L 10.28125 25.71875 L 11 26.40625 L 11.71875 25.71875 L 29.71875 7.71875 Z"
/>
</svg>
</template>
Now we also need to have a helper component called <Icon />
that we can define as global. Inside this we can make use of dynamic components and load the .vue
SVGs dynamically:
<template>
<component :is="dynamicComponent" />
</template>
<script>
import { defineAsyncComponent } from "vue";
export default {
props: {
name: {
type: String,
required: true,
},
},
computed: {
dynamicComponent() {
const name = this.name.charAt(0).toUpperCase() + this.name.slice(1);
return defineAsyncComponent(() => import(`./icons/${name}.vue`));
},
},
};
</script>
To use the global component, you can do the same thing as in the legacy code I've presented in the begining:
<icon name="check" />
That's pretty much it. What method do you prefer?
Top comments (8)
Hey, thanks for the post!
I am also using
vite-svg-loader
but my svg's are divided into sub folders, like/svg/icons
,/svg/illustrations
, /svg/badges` an so on..When using
vite-svg-loader
I can't seem to make it work with subfolders.. Would you know how to achieve that?dirty hack:
now you can use like this:
for more info see dynamic import limitations in rollup:
github.com/rollup/plugins/tree/mas...
I have resolved in issue with javascript. I know it is not the optimum solution but it's worked for me.
only limitation is it works for only single level of folder like /svg/illustrations.xyz.svg
Thank you for the follow-up!
I ended up like this.. glob helps to look through nested folders and not sure how performant this is but so far it's been working well to my surprise..
in SvgBase.vue component
Then use it like (notice only file name is given, createSvgMap will have all svg's in the defined directory path)
Thanks for you're great article. I have done the solution 3 in my Vite project and i found out it doesn't support dynamic component and loads all of the icon components. (Network Tab)
Thanks for tut!
Do you have any idea to import remote svg files inline? not to bundle any svg file.
What if you want include it as part of the section of a single page component?</p> <p>.temporary-patient {<br> background-image: url("/svg/temp-patient.svg");<br> background-color: yellow;<br> }</p>