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

SVG <style> definitions removed when loaded as component

Open distor-sil3nt opened this issue 2 years ago • 10 comments

Hi, When I include style definitions in a <style> tag, the tag including the styles are being removed when loaded as inline svg using the svg loader. Is this an intended behaviour? I would like to include style definitions and deliver them with the svg within the <svg> tag. Is there a way to preserve the style definitions within the svg?

Sample markup:

logo.svg file:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 234.86 58.25">
  <style>
    .dash-grey { fill: #706f6f; }
    .dash-rose { fill: #ea719a; }
    .dash-teal { fill: #1c9fb2; }
    .dash-teal-light { fill: #7ecbd8; }
    .dash-yellow { fill: #f7bc03; }
  </style>
  <g class="icon">
      [...]
   </g>
</svg>

What is rendered:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.86 58.25" class="logo" height="20" data-v-81440b78=""><g class="icon">[...]</g></svg>

Desired rendering:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.86 58.25" class="logo" height="20" data-v-81440b78=""><style> 
  .dash-grey { fill: #706f6f; } 
  .dash-rose { fill: #ea719a; } 
  .dash-teal { fill: #1c9fb2; } 
  .dash-teal-light { fill: #7ecbd8; } 
  .dash-yellow { fill: #f7bc03; }
  </style><g class="icon">[...]</g></svg>

The loaded SVG should include the style defintions from the SVG file.

Thanks for your help.

distor-sil3nt avatar Jun 20 '22 08:06 distor-sil3nt

Hi, Vue doesn't support

  1. Use the inlineStyles plugin from svgo. See https://github.com/svg/svgo#built-in-plugins
  2. Change the style tag to svg:style (VueJS parser doesn't recognize that so it will just ignore it and move on. But it's still valid HTML.), eg:
<svg>
  <svg:style>
    .dash-grey { fill: #706f6f; } 
  </svg:style>
</svg>

jpkleemans avatar Jun 20 '22 10:06 jpkleemans

Thx for your fast reply and the suggested solutions. I tried the second option. However, I get the following error:

Cannot read properties of undefined (reading 'type')
[vite] Internal server error: Cannot read properties of undefined (reading 'type')

The compiler doesn't seem to accept the svg namespace markup <svg:style> as valid svg markup.

distor-sil3nt avatar Jun 20 '22 11:06 distor-sil3nt

That's strange. For me it works out of the box. Could you please share (part of) your project so I can try to find out whats going wrong?

jpkleemans avatar Jun 22 '22 12:06 jpkleemans

@distor-sil3nt are you using Nuxt?

DrJume avatar Jun 23 '22 15:06 DrJume

Thx for your fast reply and the suggested solutions. I tried the second option. However, I get the following error:

Cannot read properties of undefined (reading 'type')
[vite] Internal server error: Cannot read properties of undefined (reading 'type')

The compiler doesn't seem to accept the svg namespace markup <svg:style> as valid svg markup.

Did you add the duplicate namespace in the <svg> tag?

                                        VVV
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" ...
  • https://github.com/vuejs/vue/issues/4144#issuecomment-258818137

DrJume avatar Jun 23 '22 16:06 DrJume

By default, SVGO does not strip classes which are used more than once, as it increases the output size. But it's needed to be able to apply the styles, because Vue strips the style tag away. Thats why I needed to set the onlyMatchedOnce option to false.

svgLoader({
    svgo: true,
    svgoConfig: {
        plugins: [
        {
            name: 'preset-default',
            params: {
                overrides: {
                    inlineStyles: {
                        onlyMatchedOnce: false,
                    },
                },
            },
        },
        ],
    },
})

Maybe it would be worth to include this snippet in the project README.md?

DrJume avatar Jun 23 '22 16:06 DrJume

@distor-sil3nt are you using Nuxt?

I'm using plain Vue 3, not Nuxt in this case

Thx for your fast reply and the suggested solutions. I tried the second option. However, I get the following error:

Cannot read properties of undefined (reading 'type')
[vite] Internal server error: Cannot read properties of undefined (reading 'type')

The compiler doesn't seem to accept the svg namespace markup <svg:style> as valid svg markup.

Did you add the duplicate namespace in the <svg> tag?

                                        VVV
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" ...

This actually solved the probem. With the extra namespace attribute it works without throwing errors now. Thanks a lot for your help @DrJume and also @jpkleemans

distor-sil3nt avatar Jun 23 '22 18:06 distor-sil3nt

You're welcome! ☺️

DrJume avatar Jun 24 '22 08:06 DrJume

I cheered to soon unfortunately. Instead of errors, I now get a warning in the console:

[Vue warn]: Failed to resolve component: svg:style
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement

When I try to match the svg:style tag in compilerOptions.isCustomElement it doesn't work since neither svg:style nor svg is listed in this option (handled as isNativeTag):

vite.config.ts:

import svgLoader from 'vite-svg-loader'
import vue from '@vitejs/plugin-vue'

export default defineConfig(async () => {
  return {
    plugins: [
      vue({
        template: {
          compilerOptions: {
            isCustomElement: (tag) => tag.startsWith('svg:style'),
          },
        },
      }),
      svgLoader(),
      [...]
    ],
    [...]
  }
})

In the docs it's saying:

Native HTML and SVG tags don't need to be matched in this function - Vue's parser recognizes them automatically.

https://vuejs.org/api/application.html#app-config-compileroptions

Do you have any idea on how to get rid of this warning?

distor-sil3nt avatar Jun 24 '22 16:06 distor-sil3nt

By default, SVGO does not strip classes which are used more than once, as it increases the output size. But it's needed to be able to apply the styles, because Vue strips the style tag away. Thats why I needed to set the onlyMatchedOnce option to false.

svgLoader({
    svgo: true,
    svgoConfig: {
        plugins: [
        {
            name: 'preset-default',
            params: {
                overrides: {
                    inlineStyles: {
                        onlyMatchedOnce: false,
                    },
                },
            },
        },
        ],
    },
})

Maybe it would be worth to include this snippet in the project README.md?

I had the same issue of svg styles disappearing. Updating the svgoConfig (as mentioned above by @DrJume) did the trick for me 👍

TylerMRoderick avatar Jul 01 '22 23:07 TylerMRoderick

Also had an issue similar to this where the viewbox attribute gets stripped off from some svgs. Fixed it with the properties below:

...

overrides: {
  removeViewBox: false,
},

...

f3yisayo avatar Aug 31 '22 14:08 f3yisayo