Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Having trouble dynamic import SVG #85

Open
Jiaocz opened this issue Dec 22, 2022 · 27 comments
Open

Having trouble dynamic import SVG #85

Jiaocz opened this issue Dec 22, 2022 · 27 comments

Comments

@Jiaocz
Copy link

Jiaocz commented Dec 22, 2022

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?

@acicero93
Copy link

@Jiaocz Did you ever find a solution?

@Jiaocz
Copy link
Author

Jiaocz commented Jan 11, 2023

@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.

@yooouuri
Copy link
Contributor

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 ?

@yooouuri
Copy link
Contributor

yooouuri commented Jan 12, 2023

@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.

@yooouuri
Copy link
Contributor

yooouuri commented Jan 12, 2023

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

@acicero93
Copy link

@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.

Strange, still doesn't work for me. I should have mentioned I'm trying to build using library mode, maybe thats the issue?

@BenjaminOddou
Copy link

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 😄

@gkatsanos
Copy link
Collaborator

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.

@Alex-Kozynko
Copy link

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>

@Luetzen
Copy link

Luetzen commented Sep 4, 2023

@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.

@Alex-Kozynko
Copy link

@Luetzen please debug your "icons" object.

@Luetzen
Copy link

Luetzen commented Sep 4, 2023

@Alex-front-end-developer

Thanks for the hint! I took the worng path.

icons: import.meta.glob(@/assets/icons/*.svg), seems better

image

Not it seems icons is filled but still the same error. Uncaught (in promise) TypeError: this.icons[this.name] is not a function

Any idea whats going on?

@Alex-Kozynko
Copy link

Alex-Kozynko commented Sep 4, 2023

@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.

@codiini
Copy link

codiini commented Sep 4, 2023

@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

image

But I also get the iconList[props.name] is not a function error

@Luetzen
Copy link

Luetzen commented Sep 5, 2023

@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.

@Alex-Kozynko
Copy link

Alex-Kozynko commented Sep 5, 2023

@Luetzen I'm amazed at your carelessness, your default import is the url in the first example, but the component should be :)

@Luetzen
Copy link

Luetzen commented Sep 5, 2023

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 !

@Alex-Kozynko
Copy link

@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-Kozynko
Copy link

@Jiaocz I think you can close the question, and post the last message from @Luetzen as an answer, it is quite detailed.

@codiini
Copy link

codiini commented Sep 5, 2023

@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

@Alex-Kozynko
Copy link

@codiini check what keys you have in the icons object and match them with the key call, the keys must match.

@codiini
Copy link

codiini commented Sep 5, 2023

@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.

image

@Alex-Kozynko
Copy link

@codiini try to console what returns after the call.

@codiini
Copy link

codiini commented Sep 5, 2023

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

And I got the following error
image

@alexmccabe
Copy link

alexmccabe commented Jan 10, 2024

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.

@syahrizalxs
Copy link

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?

Have you found the solution? I got same issue here

@Cyapow
Copy link

Cyapow commented Apr 24, 2024

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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests