Having trouble dynamic import SVG
Jiaocz opened this issue Β· 28 comments
I've set the default import to url
in vite.config.ts
svgLoader({
defaultImport: 'url',
}),
And then when I'm using dynamic import for a certain icon, it will cause an error like:
<template>
<component :is="icon" class="icon" />
</template>
<script setup lang="ts">
import { defineAsyncComponent } from "vue"
const props = defineProps<{ name: string }>()
const icon = defineAsyncComponent(() => import(`@/assets/svg/${props.name}.svg?component`))
</script>
Uncaught (in promise) Error: Invalid async component load result: /src/assets/svg/vue.svg
Is there any solution for this situation?
@Jiaocz Did you ever find a solution?
Nope, we've temporarily set the defaultImport
to component
and when using SVG within <img>
we manually add a ?url
suffix.
Maybe the import()
function doesn't recognize that file path suffix.
I guess I found why this won't work, because the @
doesn't get honoured in https://github.com/jpkleemans/vite-svg-loader/blob/main/index.js#L30, am I right @jpkleemans ?
@Jiaocz found the issue, don't set defaultImport
to url
, see: https://github.com/jpkleemans/vite-svg-loader/blob/main/index.js#L21-L25
The ?component
is ignored...
const icon = defineAsyncComponent(() => import(`../../assets/img/icons/${props.name}.svg?component`))
<Component :is="icon" />
Is working for me.
Im going to make a PR, to fix this.
Hmm weirdly, if you log id
the async load (id)
function, ?component
is removed from the path... Don't know if this intentionally by Vite or not. But because of the component
not present it always fallback to the default value in the settings
@Jiaocz found the issue, don't set
defaultImport
tourl
, see: https://github.com/jpkleemans/vite-svg-loader/blob/main/index.js#L21-L25The
?component
is ignored...const icon = defineAsyncComponent(() => import(`../../assets/img/icons/${props.name}.svg?component`))
<Component :is="icon" />
Is working for me.
Im going to make a PR, to fix this.
Strange, still doesn't work for me. I should have mentioned I'm trying to build using library mode, maybe thats the issue?
I have a quite similar code as @Jiaocz but working on some situations. I created a vue component TheSVG.vue
like so
<script setup lang="ts">
const props = defineProps<{ name: string }>()
const icon = defineAsyncComponent(() => import(`../assets/svgs/${props.name}.svg?component`))
</script>
<template>
<component :is="icon" />
</template>
in order to dynamically import my svgs into my pages. The problem is that the element is not available under the onMounted hook and I don't know why.
// SomePage.vue
<script setup lang="ts">
onMounted(() => {
console.log(document.querySelector(
'#big-circle > ellipse'
)) // returns null after page transition. Hard reload is working
})
</script>
<template>
<TheSVG id="big-circle" name="big-circle" />
</template>
However importing the svg directly (without dynamic import is working fine.
// SomePage.vue
<script setup lang="ts">
import bigCircle from '/assets/svgs/big-circle.svg?component'
onMounted(() => {
console.log(document.querySelector(
'#big-circle > ellipse'
)) // Is always working
})
</script>
<template>
<bigCircle id="big-circle" />
</template>
FYI I am using Nuxt3 with SSR.
My nuxt.config.ts
:
import svgLoader from 'vite-svg-loader'
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
vite: {
plugins: [
svgLoader({
svgo: false,
}),
],
}
})
If you have any idea don't hesitate ! Many thanks in advance π
Hey guys, if you could post a minimal repro link on stackblitz that'd be awesome, there's several messages in this issue but I think they may not be the same thing.
I think I found the best beautiful solution :)
Vite docs: https://vitejs.dev/guide/features.html#glob-import
<script>
import { defineAsyncComponent } from 'vue'
export default {
props: {
name: {
type: String,
required: true
}
},
data() {
return {
icons: import.meta.glob(`./**/*.svg`)
}
},
computed: {
icon() {
console.log(this.icons)
return defineAsyncComponent(() => this.icons[`./${this.name}.svg`]())
},
}
}
</script>
<template>
<component :is="icon" :class="className" />
</template>
@Alex-front-end-developer
Hey,
I tried your solution but it doesnΒ΄t work for me.
I got some issues to call the function:
I debuged like following:
icon() {
console.log(this.icons); --> Object
console.log(this.name); --> I got the name of the icon
const icon = this.icons[this.name];
console.log(typeof icon); --> undefined Uncaught (in promise) TypeError: this.icons[this.name] is not a function
return defineAsyncComponent(() => icon());
},
Can you help me out here? No idea whats going wrong.
@Luetzen please debug your "icons" object.
@Luetzen replace this.icons[this.name]
with this.icons[`/src/assecc/icons/${this.name}.svg`]
, or pass the full path to the icon in the name. You have element keys in the object, they must match when calling the function, otherwise the element simply does not exist.
@Alex-front-end-developer I'm having similar issues as well. here's my component (Vue 3)
<template>
<div>
<component :is="Icon"></component>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, computed } from "vue";
const props = defineProps({
name: {
type: String,
required: true,
},
});
const iconList = import.meta.glob(`@/assets/icons/**/*.svg`);
console.log(iconList);
const Icon = computed(() => {
return defineAsyncComponent(() =>
iconList[`../../assets/icons/svg/${props.name}.svg`]()
);
});
</script>
And here's the content of my iconList
But I also get the iconList[props.name] is not a function
error
@Alex-front-end-developer
Solution from this thread
I tested your solution. Now I get to the same error like in other examples to create svg as component (take a look on "Another Solution").
runtime-core.esm-bundler.js:2337 Uncaught (in promise) Error: Invalid async component load result: /src/assets/icons/bars-solid.svg
//IconStandard.vue
<script lang="ts">
import { defineAsyncComponent } from "vue";
export default {
props: {
name: {
type: String,
required: true,
},
},
data() {
return {
icons: import.meta.glob(`@/assets/icons/*.svg`),
};
},
computed: {
icon() {
console.log(this.icons);
return defineAsyncComponent(() => this.icons[`/src/assets/icons/${this.name}.svg`]());
},
},
};
</script>
<template>
<component :is="icon" :class="className" />
</template>
//App.vue
...
<IconStandard name="bars-solid" />
...
//vite.config.js
....
plugins: [
vue(),
svgLoader({
defaultImport: "url", // or 'url' or 'component'
svgoConfig: {
multipass: true,
},
}),
],
Another Solution
//IconStandard.vue
<script setup>
import { defineAsyncComponent } from 'vue';
const props = defineProps({
name: {
type: String,
required: true,
},
});
const icon = defineAsyncComponent(() =>
import(`/assets/icons/${props.name}.svg`)
);
</script>
<template>
<component :is="icon" class="fill-current" />
</template>
//App.vue
<template>
<nav>
<div navigation__links>
<icon name="bolt-solid"></icon>
</div>
</template>
//Vite.Config
....
plugins: [
vue(),
svgLoader({
defaultImport: "component", // or 'url' or 'component'
svgoConfig: {
multipass: true,
},
}),
],
});
runtime-core.esm-bundler.js:2337 Uncaught (in promise) Error: Invalid async component load result: /@fs/assets/icons/bolt-solid.svg
at runtime-core.esm-bundler.js:2337:31
It is not the same error but similiar I think.
@Luetzen I'm amazed at your carelessness, your default import is the url
in the first example, but the component
should be :)
Shame on me. I thought I had tried that as well, was even pretty sure. I must have missed it though.
Now it works.
Here again for others who may have had the same problem as me.
Using the Component
<IconStandard name="bolt-solid" />
SVG Component
<script>
import { defineAsyncComponent } from "vue";
export default {
props: {
name: {
type: String,
required: true,
},
},
data() {
return {
icons: import.meta.glob(`@/assets/icons/*.svg`),
};
},
computed: {
icon() {
console.log(this.icons);
return defineAsyncComponent(() => this.icons[`/src/assets/icons/${this.name}.svg`]());
},
},
};
</script>
<template>
<component :is="icon" :class="className" />
</template>
// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import svgLoader from "vite-svg-loader";
// https://vitejs.dev/config/
...
export default defineConfig({
plugins: [
vue(),
svgLoader({
defaultImport: "component", // or 'url' or 'component'
svgoConfig: {
multipass: true,
},
}),
],
});
Thanks a lot to @Alex-front-end-developer !
@codiini iconList is an object, so when you call its property, you need to enter the key, not the path, please correlate with the keys of the object on your screenshot and you will understand what's going on :)
@Alex-front-end-developer I copied the solution from @Luetzen and I'm now getting this error
Invalid async component load result: /_nuxt/assets/icons/svg/app-logo.svg
and this is my vite.config.js
file
/** @type {import('vite').UserConfig} */
import svgLoader from "vite-svg-loader";
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
vue(),
svgLoader({
defaultImport: "component",
svgoConfig: {
multipass: true,
},
}),
],
});
Any idea what the issue might be? I'd appreciate the help. Thank you
@codiini check what keys you have in the icons object and match them with the key call, the keys must match.
@Alex-front-end-developer
<script>
import { defineAsyncComponent } from "vue";
export default {
name: "BaseIcon",
props: {
name: {
type: String,
required: true,
},
},
data() {
return {
icons: import.meta.glob(`@/assets/icons/**/*.svg`),
};
},
computed: {
Icon() {
console.log(this.icons);
console.log(this.icons[`/assets/icons/svg/${this.name}.svg`]);
// return defineAsyncComponent(() =>
// this.icons[`/assets/icons/svg/${this.name}.svg`]()
// );
},
},
};
</script>
<template>
<component :is="Icon" class="fill-current"></component>
</template>
Here's the result of the both console.log
The keys match and return the correct imports.
@codiini try to console what returns after the call.
@Alex-front-end-developer So, I'm wondering whether it's an issue with using Nuxt and not Vue. I tried the same code with a Vue app, and it worked.
I also tried just importing the SVG as a component in the Nuxt app like this
<script lang="ts" setup>
import AppLogo from "../assets/icons/svg/app-logo.svg";
</script>
<template>
<AppLogo />
</template>
So the accepted workaround / solution for me didn't cut the mustard. I needed to set the defaultImport: 'url'
and keep dynamic imports. Everything outlined above did not work.
Instead, I bypassed the plugin entirely, which allowed me to remove it. Yes it's a little bit more manual work, however you get full control over everything.
function createAsyncComponent() {
const importPromise = new Promise((resolve, reject) => {
// Obviously change this to the path to your icon file location
import(`./icons/${props.iconName}.svg?raw`)
.then((mod) => resolve({ template: mod.default, name: `${props.iconName}Icon` }))
.catch((error) => reject(error));
});
return defineAsyncComponent(() => importPromise);
}
const IconComponent = computed(() => {
// Perform any validation you need to make sure that it's a valid icon
const valid = validateIconProps();
if (!valid) return null;
return createAsyncComponent();
});
After this, the plugin became unnecessary for our project. But I thought that it might be valuable for people who arrive here in the same position as me.
I've set the default import to
url
in vite.config.tssvgLoader({ defaultImport: 'url', }),And then when I'm using dynamic import for a certain icon, it will cause an error like:
<template> <component :is="icon" class="icon" /> </template> <script setup lang="ts"> import { defineAsyncComponent } from "vue" const props = defineProps<{ name: string }>() const icon = defineAsyncComponent(() => import(`@/assets/svg/${props.name}.svg?component`)) </script>
Uncaught (in promise) Error: Invalid async component load result: /src/assets/svg/vue.svg
Is there any solution for this situation?
Have you found the solution? I got same issue here
Shame on me. I thought I had tried that as well, was even pretty sure. I must have missed it though.
Now it works.
Here again for others who may have had the same problem as me.
Using the Component
<IconStandard name="bolt-solid" />
SVG Component
<script> import { defineAsyncComponent } from "vue"; export default { props: { name: { type: String, required: true, }, }, data() { return { icons: import.meta.glob(`@/assets/icons/*.svg`), }; }, computed: { icon() { console.log(this.icons); return defineAsyncComponent(() => this.icons[`/src/assets/icons/${this.name}.svg`]()); }, }, }; </script> <template> <component :is="icon" :class="className" /> </template>
// vite.config.js import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import path from "path"; import svgLoader from "vite-svg-loader"; // https://vitejs.dev/config/ ... export default defineConfig({ plugins: [ vue(), svgLoader({ defaultImport: "component", // or 'url' or 'component' svgoConfig: { multipass: true, }, }), ], });
Thanks a lot to @Alex-front-end-developer !
Late to the game here but this sorted it for me. I have trimmed the code down a bit if anyone wants it. I had to use the path from the root of the project rather than relative path.
<script setup>
import { defineAsyncComponent, defineProps, computed, useAttrs } from "vue";
const props = defineProps({
name: {
type: String,
required: true
}
});
const attrs = useAttrs();
const icon = computed(() => defineAsyncComponent(() => import(`/resources/assets/${props.name}.svg`)));
</script>
<template>
<component :is="icon" v-bind="attrs" />
</template>
None of these solutions are working for me.
Importing every SVG in the public directory isn't an option as we have 100s.
Dynamically importing them yields the following error, however: Error: Invalid async component load result: /_nuxt/@fs/__skip_vite/public/svg/icons/caret-down.svg
Here's what I'm working with:
<script setup>
const props = defineProps({
name: {
type: String,
default: '',
},
fill: {
type: String,
default: '',
},
stroke: {
type: String,
default: '',
},
})
const filePath = computed(() => {
const dirs = segmentFileName(/(\w+)\//gm)
const fileName = segmentFileName(/\S+\/(\S+$)/gm)
if (!fileName.length) return
let basePath = '/public/svg'
if (dirs.length) {
basePath = basePath + dirs.reduce((path, dir) => `/${dir}`, '')
}
return `${basePath}/${fileName[0]}.svg`
})
function segmentFileName(regex) {
const matches = []
let m
while ((m = regex.exec(props.name)) !== null) {
if (m.index === regex.lastIndex) {
regex.lastIndex++
}
m.forEach((match, groupIndex) => {
if (groupIndex === 1) matches.push(match)
})
}
return matches
}
const svgComponent = computed(() =>
defineAsyncComponent(() => import(filePath.value)),
)
</script>
<template>
<div class="svg-wrapper">
<component :is="svgComponent" />
</div>
</template>
And in nuxt.config:
import svgLoader from 'vite-svg-loader'
export default defineNuxtConfig({
...
vite: {
plugins: [svgLoader()],
},
...
}),
I'm genuinely surprised that this is still a problem 2 years later. Am I adding the svgLoader plugin incorrectly?