TIL: Using svg icon sprites with webpack

Today I learned to use convert my font icon to an svg sprite and load it within webpack.

At this point, a lot of things have been written about choosing SVGs over font icons mainly by Chris Coyier of CSS Tricks. I started reading here:

followed by this article regarding the use of svg sprites:

Wonderful, no more failed font loads, no more weird symbols. Just inline svg defined at the top of my page, always there, ready to be used. If only, I could use all the icons within my carefully crafted custom generated icon font here.

Turns out that there are solutions to the problem, I read this article by Sara Soueidan:

And I chose font-blast to convert my icon font into svg files inside the directory svg-icons:

npm i font-blast -D
./node_modules/.bin/font-blast my/font/file.svg svg-icons

I proceeded to convert it to an external svg-sprite, I am no longer very partial to grunt, preferring to use either independent tools or something that just works with webpack.

So, I went with svg-sprite-generator, another npm module that converts a directory of svg files into a single sprite. I found it easy to use:

npm i svg-sprite-generator -D
./node_modules/.bin/svg-sprite-generate -d ./svg-icons -o my-font-sprite.svg

Now, I have everything I need, I was pretty sure that I was almost done, but it turns out that what I had assumed to be the trivial exercise turned into something more. How would I load the icons into my html which is generated automatically using the html-webpack-plugin?

As long as I used the webpack-dev-server, the generated assets would not be written to disk, it would instead be in memory. Also, I learned about external svg sprites:

Its hard argue against having the browser cache the file separately. So, I needed to load the sprite from an external file, if that failed, I would have to either load it using ajax or inline it within the document.

I tried using the file-loader and was looking at various loaders here:

when I came across this:

and the svg-sprite-loader which allows loading of svg files using ES2015 imports or CommonJS require statements. It loads the file and provides an id which can be used to reference the icon.

It converts all the svg-files into a sprite and places them into the head of the document at build time. It can also do cache busting by suffixing the id of the icon with the hash of the build.

When you load an svg using either a

I added it to my config and everything was awesome again, I could load each file individually:

import tomato from ‘../images/tomato.svg’

It also allows me to load all my files at once:

I also created the React stateless functional component for Icon as suggested:

and I was good to go, but not before I had to do one more thing. SVGs are usually generated with a lot of namespaces, additional markup, empty attributes and so on. Before rendering, the svg needs to be optimized, use the svgo-loader with the svg-sprite-loader as suggested here:

For the sake of completeness, let me also add my final webpack config:

Ignore the npm-install-webpack-plugin, it is a particular quirk of mine, I do not like manually installing dev dependencies manually.


Since this article was published, webpack2 was released requiring changes to the configuration above. Assuming that you dont need babel-loader, css-loader and other things for the sake of convenience(or because, the author is too lazy):

Updated config for svg-sprite-loader

The svg-sprite-loader was at 0.x when this article was published, the loader has received a lot of attention since then. It now stands proudly at 3.0.5, its new superpowers include adamantium claws and the ability to take many of the scenarios you might throw at it.

Let’s look at these changes together:

  1. The plugin’s output has changed, instead of just providing us with an id it uses an id along with a viewbox . Our Icon component needs to change a little:

2. The plugin has a few more tricks up its sleeve now, you can get it to generate a component for you automatically.

I have pulled the example in the repo into a gist for quick reference:

3. Server Side rendering support was included in the previous version, but we never discussed it, using this example from the repo, I present: