oliverfindl/vue-svg-inline-loader

src binding not finding svg file

DavidVII opened this issue ยท 32 comments

Hello there, I'm not sure if this is an issue with the loader or merely a lack of my understanding. It appears that when I attempt to dynamically load an SVG using the v-bind:src approach on the image tag, the loader cannot find it.

Loading SVG from src path

<img svg-inline :src="`../../static/images/icon-${game.name}.svg`" />

If I write it in normally src="../../static/images/icon-basketball.svg" it works.

Loading SVG from build path
I've also tried this:

<img svg-inline :src="`/images/icon-${game.name}.svg`" />

But it ends up just loading up an image tag with the SVG as it's src instead of inlining the SVG.

Any ideas?

Hello,

Its not working like that. This Webpack loader only parses static files. It does not execute any Vue logic at this point. That happens later, when these files are served from server and parsed in client (browser). So in this loader its not possible to use Vue bindings, because binded variables in template would not get replaced prior to Vue execution in client (browser).

Its probably good idea to mention it in readme file.

You are probably looking for some kind of vue-plugin (that will dynamically inject SVGs to DOM based on your current data) and not webpack-loader.

If you have any questions, feel free to ask.

Thanks, @oliverfindl. I appreciate your response, and it all makes sense. I think it would be convenient to be able to use Vue bindings, but understand why it's not.

Readme file updated.

Closing this issue.

I have a similar problem.
I solved it as follows:
in template

        <div
          v-for="(prj, index) in projects"
          :key="index"
        >
          <div class="align-center" v-html="prj.svg"></div>

in js

export default {
  data() {
    return {
      projects: [
        {
          svg: '<img svg-inline src="@/assets/i/icon005.svg" alt="">',
        },
        {
          svg: '<img svg-inline src="@/assets/i/icon005.svg" alt="">',
        },
        {
...

and all working fine =)

Hello,

yes this will work fine, but you have to define all img tags in your source code. Eg.: It won't work, for resources fetched with ajax.

Thanks

this does not work at all, the svg-inline directive is just ignored, so the SVG file does load, but it's not inlined.

Hello,

Could you describe in more detail what exactly is not working? Even better if you could provide some dummy repo which has this issue replicable.

Thanks

Hello, i'm sorry I should have been more explicit. I think everything is working as intended, it's newbie78's workaround that is not working.

Hello,

I just tested this workaround and its still working for me.

Template:

<template>
	<div class="root-wrapper">
		<div v-html="image" class="logo-wrapper" />
	</div>
</template>

Script:

export default {
	name: "App",
	data: () => ({
		image: `<img svg-inline svg-sprite src="@/assets/logo.svg" class="logo" />`
	})
};

Please can you try this example and give feedback if it works for you?

Thanks

My bad, this does work. I was trying to declare all my path in a for loop, but this only works if the declaration is static. So, this will work:

<template>
	<div class="root-wrapper">
		<div v-html="getImage()" class="logo-wrapper" />
	</div>
</template>
export default {
	name: "App",
	methods: {
		getImage() {
                    return '<img svg-inline svg-sprite src="@/assets/logo.svg" class="logo" />'
                }
	})
};

but this will not:

<template>
	<div class="root-wrapper">
		<div v-html="getImage('someItem')" class="logo-wrapper" />
	</div>
</template>
export default {
	name: "App",
	methods: {
		getImage(item) {
                    return '<img svg-inline svg-sprite src="@/assets/' + item + '.svg" class="logo" />'
                }
	})
};

In this second example, the SVG will be displayed but will not be inline.

Hello,

sure it wont work like that. This Webpack loader only parses static Vue SFC files (something.vue) one by one and replaces any SVG references found in src attribute of img tags with its actual contents if found on filesystem. In your example it wont replace because file .../assets/${item}.svg does not exists. Loader wont execute any Vue logic, that happens later in browser (or SSR).

Hope this brings some light into this issue.

If you have any more questions, feel free to ask.

Thanks

Yes, I understood that from your first answer to this post, I'm just a little slow ;) thank you for the additional explanation anyway !

This working if data create in component? But vuex also not working?

Hello,

If you mean using variables via Vuex mapGetters method, this is still Vue variable and applies exactly what is written above.

If you mean workaround using img tags directly in Vue data (or in this case in Vuex store state), this won't work because as mentioned above in my last answer, this loader parses only static Vue SFC files (*.vue), but not javascript files (*.js). I'm not aware of simple way to distinguish Vuex store file and/or Vuex store module files from other javascript files. Even if I can, there will be no way for using inline SVG sprites with this approach.

As mentioned in my first answer, you guys are probably looking for some kind of vue-plugin (that will dynamically fetch and inject SVGs to DOM based on your current data) and not webpack-loader.

If you have any more questions, feel free to ask.

Thanks.

I am trying to do something like this:

<template>
  <div class="c143_figure">
    <!-- <img svg-inline class="icon" :src="deck.svgFileName" alt /> -->
    <div id="testId" v-html="svgImage"></div>
  </div>
</template>

<script>
import { mapGetters } from "vuex";
export default {
  name: "DeckPlanSvgContainer",
  computed: {
    ...mapGetters(["deck"]),
    svgImage: function() {
      return `<img svg-inline svg-sprite class="icon" src="${this.deck.svgFileName}" alt />`;
    }
  }
};
</script>

<style lang="scss" scoped>
</style>

and is not working

Hello,

as mentioned above, you can't use Vue bindings/variables with this loader. For more info please read all comments above.

Thanks

afuno commented

@oliverfindl

<template>
  <div v-html="img"></div>
</template>
export default {
  props: {
    name: {
      type: String,
      required: true
    }
  },

  computed: {
    path() {
      // return `@images/icons/${this.name}.svg`
      // OR
      return require(`@images/icons/${this.name}.svg`)
    },

    img() {
      return `<img svg-inline src="${this.path}" />`
    }
  }
}

Does not work. I also tried methods (just in case).


return `@images/icons/${this.name}.svg`
<img svg-inline="" src="@images/icons/dog.svg">

This line does not work at all, because Vue cannot even understand what it is. It can not find the file. I have to use only require.

Hello,

I will try to explain it one more time as simple as possible.

This Webpack loader only parses static files. That means, it takes all your *.vue files and tries to locate all *.svg sources in image tags in those files. It doesn't perform any Vue specific logic, because that happens later in browser (or in SSR process).

As for using @ alias, that's Webpack stuff. It has nothing to do with Vue.

If you have any more questions, feel free to ask.

Thanks

Hello,

This function is not accessible in Webpack loader. It can be used in files, that are parsed with Webpack (your Vue app) which will again lead to Vue bindings usage.

If you have some other usage in mind, please write about it in more detail.

Thanks

The point was that the following works, thanks to require with expression.

<template>
	<img :src="path" />
</template>
<script>
export default {
	computed: {
		path() {
			return require('shared/icons/' + this.name + '.svg')
		},
	},
}
</script>

Was wondering if someone the same mechanism could be leveraged in vue-svg-inline-loader.

Hello,

your example works in client/browser using custom Webpack require function, which is injected into your bundle. Therefore Vue can evaluate your variable and call this function to fetch correct svg file.

In other words, this Webpack require function is just syntax sugar for loading resources with ajax. Imagine something like this (just a concept):

export default {
	computed: {
		async path() {
			try {
				return await (await fetch('shared/icons/' + this.name + '.svg')).text();
			} catch(e) {
				console.error(e);
			}
		}
	}
};

Looking at your example, I think even this could work:

<div v-html="require('shared/icons/' + name + '.svg')"></div>

But still, all this action happens in client/browser. Loader performs this action at build time without any Vue logic available.

Only input for this loader is literally your single *.vue file (Webpack loaders works per file). There is no way to determine what value could your variable have at render time (so it could be injected and rendered) and what possible values can it have later (so it could inject this svg files too and then somehow swap them - and ofcourse, we can start talking about performance at this point). Imagine getting this value from another *.vue or *.js files, event bus, vuex, get params, v-model or from ajax response (not to mention different variants with authenticated users)... There is just no way to determine your value at build time. Probably could be possible with SSR, but that uses minority of user base.

Whole point of this loader is, to get your svg files injected right into your built source code instead of img tags. Your example just downloads those files and then injects them into rendered html.

Actually, I'm currently working at such Vue plugin, which would solve this particular issue, although at cost of performance compared to this loader.

If you have any more questions, feel free to ask.

Thanks

You seem to have misunderstood the purpose of my example. It actually uses no ajax, but a simple img tag. The require returns the path for resource, after being processed by webpack at build time. But how does webpack know the name of file, if it is only determined at run time? It doesn't. But it loads and processes every file inside the directory at build time, and at runtime it returns the correct path, out of the map generated at build time. Your loader could potentially do the same.

Hello,

sorry I really misunderstood it.

But still, you need to keep in mind scope of Webpack and scope of Webpack plugin, which got access only to vue files without any hierarchy or relations. Input for this loader is your *.vue file as string and ouput is modified string (or error).

By current approach (swap img tag with svg tags if possible) with vue-directive (or without, if you set strict mode to false in options). This approach results in minimal/no changes to your files.

Creating behavior similar to Webpack require function could go 3 ways (at least I got in my mind right now):

  • Create an inject custom function (same as Webpack do) which will output whole svg based on (dynamic) path to the file. This will require user to use such function in source code (probably with computed properties or v-html directive on parent element) or loader could try to inject it into code itself, which is something I won't do. I don't want to modify users source code and probably break something I'm not aware of. This function (with all possible svg files) would need to be injected into each vue file. It's possible, it will introduce some breaking changes. In short, it could be exactly like Webpack require function, but it would work per file instead per whole project.
  • Same as first variant, but let logic part to Vue. Loader will inject all possible svg files with v-if directive. Which will result to all vue files having injected all svg files (because variable value can go up and down in file tree - that can be restricted in first variant with require.context() like function). Imagine having unpacked font-awesome in your project and getting it inject into all your *.vue files, although you are using only 2 icons. Better situation (but still bad) would be using sprites, which is currently optional option.
  • Last possible variant to do it (which is similar as variant 2), is to provide each img tag with array of all possible variable values (literals) as data attributes and then inject svg files with all possible combinations (probably using eval() to interpolate variables in path string), each with v-if directive with logical ANDs between current variable combination. Which again won't be much useful, because user need to specify all possible values beforehand (and bet that many users would try to specify them via another Vue bindings, like they are trying now). This will end up literally in same results, as is current solution/workaround - to define all possible img tags with v-if directive. Only benefit for user would be less code, but on other hand it will be more code in loader, therefore more places for bugs and race conditions.

I don't think, any of variants above is worth of coding as it would introduce much more complex logic to this loader (which I try to keep as simple as possible) and much larger file size per file (except variant 3, which would be only syntax sugar for something, that is possible right now).

If you have some other (non-breakable) solution in mind, feel free to explain it here.

Thanks

Hey Oliver, given that most things in a Vue component will be dynamic (rare that you need a one-shot inline SVG) isn't that limiting the usefulness of the plugin?
To put it in a different perspective : if I have to manually add the static file, what's the difference from hard-importing each img src directly and copy pasting the inline svg code? (just thinking out loud, dont mean to reduce the work)

Hello,

while it is true, that majority of use cases are based on displaying svg image is based on dynamic rules, Webpack runs before any Vue logic is able to execute (excluding SSR, which is minority of use cases). Therefore there is no simple way to know, which svg files should Webpack include into bundle. You can read my last message in this issue for more details and possible solutions.

I'm currently working on Vue plugin, which will target this issue, of course with cost of performance.

For your concerns about usefulness of this loader, currently you have more approaches you can use to inline your svg files:

  • using Webpack require function together with v-html directive, which will increase your build time and bundle size (demo here, read readme first).
  • using different Webpack loader (e.g.: vue-svg-loader), which require to manually import svg files and register them as components before you can use them. This can get pretty messy, if you are using lot of svgs.
  • using Vue plugin (e.g.: vue-simple-svg). I don't have any experience with it. I'm working on something similar (based on this loader), but more advanced with more features.
  • manually copying svgs into your code, which is very hard to maintain, when you are making changes to svg, that is copied into multiple components.

At time I was writing this plugin, I had to solve situation, where I had to show one of 3 possible svg images about 100+ times (was user configurable). So at that time, it was pretty straightforward to me to do v-if, v-else-if and v-else together with inline sprites. Time showed me, that people want use this loader different way I designed it. Therefore I'm working on Vue plugin version of this loader as I stated above.

If you have any more questions, feel free to ask.

Thanks

All clear, appreciate the response! I am thinking if there's a pure-CSS way to handle this without involving JS at all. (as in, use SVG as background-image)

This extension helped me out in the end. It takes a different approach but does the job.

Hello,

as for pure CSS solution, I know only about using icofonts (built from your svgs), but I don't like this approach.

If you have any more questions, feel free to ask.

Thanks

wng97 commented

Hi,
i am having the same problem,
it work fine without the inline SVG, but once i add in the inline svg it won't render out the image:

<div class="col-12 col-md-7">
    <figure id="vehicle_map">
        <svg version="1.1" xmlns="http://www.w3.org/2000/svg" :viewBox="computed_viewBox" >
            <img svg-inline :src="showImage" width="100%" height="100%" />
            <template v-for="(polygon) in vehicle_parameters.polygons">
                <g :id="polygon.name" class="hover_group" opacity="1" :key="polygon.name">
                <polygon
                    fill="Grey"
                    style="opacity: .6;"
                    :points="polygon.points"
                    @click="onClickMap(polygon.name)"
                />
                <text
                    :x="polygon.text_position_x"
                    :y="polygon.text_position_y"
                    fill="blue"
                >{{polygon.name}}</text>
                </g>
            </template>
        </svg>
    </figure>
</div>

showImage() {
    if (!this.bodyTypeImagePath) {
    return;
    }
    var fileName = this.bodyTypeImagePath;
    // console.log(fileName)

    // return fileName;
     return require(`../../assets/car_line_diagram/${fileName}.svg`);
},

anyone can help please? i have been stuck on this for weeks :(

Hello,

I explained this issue multiple times here already... Please read whole tread.

As for your example <img svg-inline :src="showImage" width="100%" height="100%" />, Webpack doesn't know what showImage should be. To be more specific, it doesn't even know, what to do with :src, let alone how to resolve its dynamic value... Please bear in mind, this loader runs only at build time in Webpack context and doesn't know shit about Vues logic (or any other javascript logic present in that file). This loader expects src attribute with valid path to SVG file in it. Everything else are just some additional attributes, that would be transferred to SVG replacement.

I made similar plugin vue-svg-inline-plugin, which aimed to solved this issue, but also failed at this task... However, at least it works with first dynamic value in :src attribute and then it loses its Vue binding. If this suits your use case (need to be dynamic only 1 time), feel free to test it.

If there would be some interest, I could create different version, that would work with dynamic source value, but it will have some other issues because of its nature (no placeholder images, missing images until they are fetched from server, jumping content, etc.).

If you have some more (specific) questions, feel free to ask here.

Thanks.

wng97 commented

st it works with first dyna

Oh๏ผŒ thanks Oliver, my savior! yeap, I did look through all the comment before i posted this question, just trying to ask its there any possibility hahaha, but I think your new plugin should be suit in my case, since I just need to use it once. once again thanks for your efforts @oliverfindl