avenmia
avenmia

Reputation: 2605

How do I import an svg in Vue 3?

I tried following: https://github.com/visualfanatic/vue-svg-loader/tree/master

but there's a version conflict with vue-template-compiler since that's used in Vue 2.

I tried: https://github.com/visualfanatic/vue-svg-loader

but I'm missing a specific vue dependency.

I noticed there's a caveat with using typescript and you need to declare the type definition file. However, I still get "Cannot find module '../../assets/myLogo.svg' or its corresponding type declarations."

Here's what I added:

vue.config.js

module.exports = {
  chainWebpack: (config) => 
  {
    const svgRule = config.module.rule('svg');

    svgRule.uses.clear();

    svgRule
      .use('vue-loader-v16')
      .loader('vue-loader-v16')
      .end()
      .use('vue-svg-loader')
      .loader('vue-svg-loader');
  },
  configureWebpack: process.env.NODE_ENV === 'production' ? {} : {
    devtool: 'source-map'
  },
  publicPath: process.env.NODE_ENV === 'production' ?
    '/PersonalWebsite/' : '/'
}

shims-svg.d.ts

declare module '*.svg' {
  const content: any;
  export default content;
}

MyComponent.vue

<template>
  <div>
     <MyLogo />
  </div>
</template>

<script lang="ts">
import * as MyLogo from "../../assets/myLogo.svg";

export default defineComponent({
  name: "MyComponent",
  components: {
    MyLogo
  },
  props: {
    
  },
  setup(props)
  {
    return {
      props
    };
  }
});


</script>

Upvotes: 21

Views: 49083

Answers (5)

Bryce Rakop
Bryce Rakop

Reputation: 184

It's true using an IMG tag will render your SVG, but for anyone expecting SVG features, you won't get that with an IMG tag. There's a more expanded tutorial here: https://blog.logrocket.com/using-svg-and-vue-js-a-complete-guide/ (check out number 3)

But essentially, your best option for custom SVG's is to build them as a Vue Component and import that component. This will give you full flexibility in controlling stroke and fill properties as well as open up opportunities to pass in your Vue variables to effect changes to the SVG. Take a look at example 3 on that site.

The big thing this fixed for me was being able to pass the CSS color property to the SVG and then have the SVG stroke="currentColor" actually recognize it. When the SVG is loaded with an img tag that color modifier is never honored, I just end up with a black SVG.

Examples from my use (I needed a Tabler icon that wasn't in the Tabler Vue 3 library I'd imported) So I went to https://tabler-icons.io/ and downloaded the one I wanted (alert-circle-filled in this case) which gave me the SVG:

<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-circle-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
  <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
  <path d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-4.99 11.66l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z" stroke-width="0" fill="currentColor" />
</svg>

I then built this component, which is just really a wrapper for the SVG from that:

<template>
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-circle-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
  <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
  <path d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-4.99 11.66l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z" stroke-width="0" fill="currentColor" />
</svg>
</template>

<script>
export default {
   name: 'AlertCircleFilled',
}
</script>

<style scoped>
.icon-tabler-alert-circle-filled {
    /* Scoped CSS here */
}
</style>

Lastly, I just import and use the Vue component, and now my CSS color is correctly used by the SVG:

import AlertCircleFilledIcon from './AlertCircleFilled.vue';
...
components: {AlertCircleFilledIcon, ... },
...
<AlertCircleFilledIcon class="btnIcon" style="color:blue;"/>

Upvotes: 4

user21049621
user21049621

Reputation: 41

vue-svg-loader is not compatible with vue 3. To import svg and use it as a component, simply wrap the contents of the file in 'template'

In component:

<template>
  <div class="title">
    <span>Lorem ipsum</span>
    <Icon />
  </div>
</template>

<script>
import Icon from '~/common/icons/icon.svg';

export default {
  name: 'PageTitle',
  components: { Icon },
};
</script>

Webpack:

{
   test: /\.svg$/,
   use: ['vue-loader', path.resolve(__dirname, 'scripts/svg-to-vue.js')],
}

scripts/svg-to-vue.js:

module.exports = function (source) {
  return `<template>\n${source}\n</template>`;
};

Upvotes: 4

retro_m
retro_m

Reputation: 1

Example from fresh installed vue.js 3.2:

<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125"/>

Upvotes: -2

Yom T.
Yom T.

Reputation: 9180

Actually SVGs are supported right out of the box with Vue CLI. It uses file-loader internally. You can confirm it by running the following command on the terminal:

vue inspect --rules

If "svg" is listed (it should be), then all you've got to do is:

<template>
  <div>
    <img :src="myLogoSrc" alt="my-logo" />
  </div>
</template>

<script lang="ts">
  // Please just use `@` to refer to the root "src" directory of the project
  import myLogoSrc from "@/assets/myLogo.svg";

  export default defineComponent({
    name: "MyComponent",

    setup() {
      return {
        myLogoSrc
      };
    }
  });
</script>

So there's no need for any third party library—that is if your sheer purpose is only to display SVGs.

And of course, to satisfy the TypeScript compiler on the type declaration:

declare module '*.svg' {
  // It's really a string, precisely a resolved path pointing to the image file
  const filePath: string;

  export default filePath;
}

Upvotes: 26

Daniel
Daniel

Reputation: 35684

Can't say for sure, since I haven't tried with ts, but as posted here

this should work.

declare module '*.svg' {
    import type { DefineComponent } from 'vue';
    const component: DefineComponent;
    export default component;
}

I see you're using:

import * as MyLogo from "../../assets/myLogo.svg";

I believe that should be:

import MyLogo from "../../assets/myLogo.svg";

Upvotes: 1

Related Questions