Using dynamic image paths in .vue files

Using dynamic image paths in .vue files

26 May 2021 webpack vue nuxt javascript

When using Vue via Vue CLI or Nuxt, both of which use Webpack, how can we use dynamic rather than static image paths? In this article I'll be taking a look at this.

Huge props to Alexander Lichter, whose article on this subject prompted me to write this post. This is essentially my (cut-down) retelling of the main points from his article.

The problem 🔗

It's easy enough to output static images. We can reference these relatively, absolutely or via an alias pointing to our assets directory.

<template> <img src='../assets/some-image.png'> </template>

But what if we need to output a dynamic image? If you're like me, you'll probably fall into the trap of trying something like this:

<template> <img :src='`../assets/${img}`'> </template> <script> export default { data() { return { img: 'some-image.png' } } } </script>

But that doesn't work! What's up?

The explanation 🔗

The reason has to do with Webpack, which both Nuxt and Vue CLI use.

If you're unfamiliar with Webpack, it's essentially a means of "building" your project into a web-ready set of files, with the advantage that it can run all sorts of processes during that build phase, such as minifying your code, parsing SASS into CSS, and much more. This allows you, the developer, to work in an environment that is abstracted away from the ultimate set of files that are served to your web users, e.g. the Nuxt framework and its .vue files. They aren't part of the end build (browsers wouldn't know how to parse them); they're instead sent to Webpack to be turned into derived HTML, JS and CSS.

Now, under the hood, Webpack resolves file paths to modules. In other words, when discovering references to assets that are permitted to be included in the build (by the Nuxt/Vue CLI Webpack config), it resolves them as modules. And since Webpack is built on Node.js, that means require().

But here's the crucial point: This works fine for static paths, but Webpack isn't in the business of parsing our dynamic v-bind directives (that's Vue's job) - and so, because Webpack can't know what those dynamic paths will resolve to, it leaves them alone.

The solution 🔗

The solution has already been hinted at. If we know that Webpack resolves paths as modules, via require(), yet ignores dynamic paths whose ultimate value it can't be sure of, it falls to us to do the convert those dynamic paths to modules ahead of time.

What does this look like? This:

<img :src='require(`../assets/${img}`)'>

That way, by the time Webpack comes onto the scene, the path has basically already readied as a module.

Obviously, this is pretty ugly, so we can farm this work out to a method or computed property:

<template> <img :src='imgPath'> </template> <script> export default { data() { return { img: 'some-image.png' } }, computed: { imgPath() { return require(`../assets/${this.img}`); } } } </script>

Et voila!

Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!