vue-svg-loader icon indicating copy to clipboard operation
vue-svg-loader copied to clipboard

Nuxt 3 support

Open jerryjappinen opened this issue 3 years ago • 9 comments

Nuxt 3 is still not quite ready for prime time, but it's getting close. SVGs are a crucial component of any web app, so having vue-svg-loader working on Nuxt 3 will be a big step.

While Nuxt 3 is still in progress, I wouldn't think it would be an unreasonable effort to update compatibility and docs to enable Nuxt 3 usage.

So far I wasn't able to get vue-svg-loader to work. I'm getting this error message after double-checking the configuration on nuxt.config.ts and my usage in the component file (I'm trying to load SVGs as components).

Screenshot 2022-02-09 at 0 33 12

There might be various issues in my current setup on Nuxt 3, but it would be great if we could confirm the status, make the necessary updates (if any) and update docs to confirm the correct way to implement the various SVG loading options on Nuxt 3.

Docs for Nuxt 3: v3.nuxtjs.org

jerryjappinen avatar Feb 08 '22 22:02 jerryjappinen

Related: https://github.com/nuxt-community/svg-module/issues/86

jerryjappinen avatar Feb 08 '22 22:02 jerryjappinen

@jerryjappinen I was able to get this working with Nuxt 3 using the0.17.0-beta.2 version of vue-svg-loader. My nuxt config looks like this:

export default defineNuxtConfig({
  vite: false,
  hooks: {
    'webpack:config': (configs) => {
      configs.forEach((config) => {
        const svgRule = config.module.rules.find((rule) => rule.test.test('.svg'))
        svgRule.test = /\.(png|jpe?g|gif|webp)$/
        config.module.rules.push({
          test: /\.svg$/,
          use: ['vue-loader', 'vue-svg-loader'],
        })
      })
    }
  }
}

Let me know if that helps!

cyruscollier avatar Feb 13 '22 03:02 cyruscollier

Thanks @cyruscollier . I switched to vite-svg-loader which seems to work fine as well:

import svgLoader from 'vite-svg-loader'

export default defineNuxtConfig({

  vite: {
    plugins: [
      svgLoader({
        /* ... */
      })
    ]
  }

})

jerryjappinen avatar Feb 14 '22 20:02 jerryjappinen

@jerryjappinen But vite-svg-loader cannot into dynamic import. Like

setup(props) {
    const currentIcon = computed(() => {
      return defineAsyncComponent(() => import(`@/assets/icons/16/${props.name}.svg?component`))
    }).value

    return {
      currentIcon
    }
  }

How do you import the icons component? Each one separately?

grindpride avatar Feb 15 '22 18:02 grindpride

@jerryjappinen But vite-svg-loader cannot into dynamic import. Like

setup(props) {
    const currentIcon = computed(() => {
      return defineAsyncComponent(() => import(`@/assets/icons/16/${props.name}.svg?component`))
    }).value

    return {
      currentIcon
    }
  }

How do you import the icons component? Each one separately?

Yeah I usually import them as components, either each one separately or in one Icon component which can then toggle the icon by prop (and do other things, like multiple icon states, rotations etc).

In a regular app/site with a limited set of commonly used icons I think it's fine. Haven't really needed dynamic imports that much.

jerryjappinen avatar Feb 15 '22 19:02 jerryjappinen

@grindpride I could use dynamic component to load the svgs by this:

<template>
  <component v-if="tag" :is="tag"></component>
</template>

<script>
import { shallowRef } from 'vue';
export default {
  props: {
    name: {
      type: String,
      required: true
    },
  },

  setup(props) {
    let tag = shallowRef('');

    // Note this: `@` or `~` wont work
    import(`../assets/svg/${props.name}.svg`).then(module => {
      tag.value = module.default;
    });

    return {
      tag
    };
  },
}
</script>

// Usage
<base-icon name="svg-name.svg"/>

More info: https://stackoverflow.com/questions/65950655/dynamic-component-in-vue3-composition-api

yuhua-chen avatar Nov 16 '22 02:11 yuhua-chen

@grindpride I could use dynamic component to load the svgs by this:

<template>
  <component v-if="tag" :is="tag"></component>
</template>

<script>
import { shallowRef } from 'vue';
export default {
  props: {
    name: {
      type: String,
      required: true
    },
  },

  setup(props) {
    let tag = shallowRef('');

    // Note this: `@` or `~` wont work
    import(`../assets/svg/${props.name}.svg`).then(module => {
      tag.value = module.default;
    });

    return {
      tag
    };
  },
}
</script>

// Usage
<base-icon name="svg-name.svg"/>

More info: https://stackoverflow.com/questions/65950655/dynamic-component-in-vue3-composition-api

Is there any security concern when providing the client access to telling server what path to load from?

digitalcortex avatar Nov 29 '22 11:11 digitalcortex

In Nuxt 3, you don't need neither vue-svg-loader, nor vite-svg-loader, but can instead create a custom component utilizing Vite's glob import:

<template>
  <span v-if="icon" class="h-[1em] w-[1em]" v-html="icon" />
</template>

<script setup lang="ts">
const props = defineProps<{
  name?: string
}>()

// Auto-load icons
const icons = Object.fromEntries(
  Object.entries(import.meta.glob('~/assets/images/*.svg', { as: 'raw' })).map(
    ([key, value]) => {
      const filename = key.split('/').pop()!.split('.').shift()
      return [filename, value]
    },
  ),
)

// Lazily load the icon
const icon = props.name && (await icons?.[props.name]?.())
</script>

johannschopplich avatar Dec 09 '22 13:12 johannschopplich

In Nuxt 3, you don't need neither vue-svg-loader, nor vite-svg-loader, but can instead create a custom component utilizing Vite's glob import:

<template>
  <span v-if="icon" class="h-[1em] w-[1em]" v-html="icon" />
</template>

<script setup lang="ts">
const props = defineProps<{
  name?: string
}>()

// Auto-load icons
const icons = Object.fromEntries(
  Object.entries(import.meta.glob('~/assets/images/*.svg', { as: 'raw' })).map(
    ([key, value]) => {
      const filename = key.split('/').pop()!.split('.').shift()
      return [filename, value]
    },
  ),
)

// Lazily load the icon
const icon = props.name && (await icons?.[props.name]?.())
</script>

This component causes a recursive query for every svg that is in the ~/assets/images folder. In other words, every time this component is loaded, it re-queries ALL of the SVGs.

What I did was just loaded the icons into the state using pinia, then called the function to retrieve the SVG file data from the component instead (apologies for the tailwind css stuff, I'm in a time crunch):

AppStore.js

import { defineStore } from "pinia";
export const useAppStore = defineStore("AppStore", {
  state: () => {
    return {
      icons: [Object],
    };
  },
  actions: {
    async fetchIcons() {
      const i = Object.fromEntries(
        Object.entries(
          import.meta.glob("~/assets/svg/*.svg", { as: "raw" })
        ).map(([key, value]) => {
          const filename = key.split("/").pop().split(".").shift();
          return [filename, value];
        })
      );
      this.icons = i;
    },
  },
});

SvgIcon.vue Component

<template>
  <div class="pt-1">
    <span v-if="icon" class="h-[1em] w-[1em]" v-html="icon" />
  </div>
</template>

<script lang="ts">
import { useAppStore } from "../stores/AppStore.js";
export default defineComponent({
  props: {
    name: String,
  },
  async setup(props) {
    const appStore = useAppStore();
    var arr = Object.entries(appStore.icons).filter((i) => {
      return i[0] === props.name;
    });
    const icon = await arr[0][1]?.().then((res: any) => {
      return res;
    });
    return {
      appStore,
      icon,
    };
  },
});
</script>

ikluhsman avatar Feb 03 '23 02:02 ikluhsman