Solving the Unsolvable: SVG Icons With React/Preact & Webpack

Sasha Koss
3 min readJul 24, 2017

--

Icons always been an issue in front-end world. Every time we think that the problem is finally solved, the industry makes another swing. Requirements and possibilities change, so yet another superior solution appears. Image sprites, fonts, SVG sprites… you name it.

Recently I’ve published stories were I’ve shown how to import CSS and any asset as React/Preact component. It inspired me to explore the topic even further and find a way efficiently yet concise import SVG as components.

🥇 Inline SVG

At the moment, inline SVG is the best approach to icons in the web. It does not only solve the problem with HiDPI displays but unlike SVG-as-img allows to control the image with JavaScript: change colors, animate, etc. For more details about that, I recommend to watch a video by Front End CenterWhy Inline SVG is Best SVG”.

Loading SVG with Webpack

The approach is quite labor-intensive as you need to insert the SVG source manually, but thankfully, with webpack, it’s as easy as import a file:

import svgSource from ‘./image.svg’

There are few solutions for that:

  • Popular svg-inline-loader maintained by the webpack team.
  • Unheard svg-loader built by my friend (who just got married, congratulations, Andrei!).

I’ve decided to stick to the latter as I already had experience with it and also due to the fact that it’s built with dangerouslySetInnerHTML in mind, so the exported object doesn’t contain the root svg element.

The Dilemma

Initially, I’ve simply wrapped imported SVGs into a component and passed the icon name as a prop, but the solution has an obvious downside: the build size grows with the number of icons regardless of whether you use them all or not:

import Icon from 'app/UI/_lib/Icon'<Icon type='danger' size='big' color='small' />

For instance Font Awesome 5 SVG collection weights 6.3 Mb that is clearly not an option.

The solution for that is to import the component and icon file separately and combine them manually:

import Icon from 'app/UI/_lib/Icon'
import dangerIcon from 'app/UI/_lib/danger.svg'
<Icon source={dangerIcon} size='big' color='small' />

Some would end the story right here, but not me. We can do better!

Importing SVG as React/Preact Component

To achieve my goal, first I’ve built a loader that converts SVGs to React/Preact components:

import DangerIcon from 'app/UI/_lib/danger.svg'<DangerIcon fill='#f00' width='1rem' />

Wrapping Icons Into HoC

In the real world, the icon would have more props (e.g. style — light/regular/solid), and as we want to not repeat ourselves and keep UI consistent, we have to limit the color and size to the predefined set.

The solution for that would be a high-order component, that returns us back to the initial solution 🤦‍:

import Icon from 'app/UI/_lib/Icon'
import DangerIcon from 'app/UI/_lib/danger.svg'
<Icon component={DangerIcon} size='big' color='red' />

I started to wonder, what if we can automate it using the power of webpack loaders? After a while, I found a way and built hoc-loader that allowed me to ditch the glue code:

import DangerIcon from 'app/UI/_lib/danger.svg'<DangerIcon size='big' color='red' />
//=> <svg fill='#f00' width='1rem'>...</svg>

Here is sample configuration that I’ve extracted from my project:

const iconsPath = path.resolve(process.cwd(), 'app/UI/_lib/Icon')
...
{
test: /\.svg$/,
use: [
{
loader: 'hoc',
options: {
useDefault: true,
path: path.join(iconsPath, 'index.jsx') // 👈 Path to HoC
}
},
'desvg/react', // 👈 Convert SVG to components
'svg'
],
include: iconsPath
},
...

And finally, the HoC:

import React from 'react'
import classNames from 'classnames'
export default function (Svg) {
return function Icon ({
size = 'medium',
color = 'black',
style = 'solid'
}) {
return (
<div
className={classNames(
'Icon',
`is-${size}`,
`is-${color}`,
`is-${style}`
)}
>
<Svg fill='currentColor' />
</div>
)
}
}

This way I’ve got simple and concise way to deal with icons that allow to make the build as small as possible and keep code clean and tidy.

For more info about usage and configuration see desvg and hoc-loader READMEs.

If you have any questions or need help, don’t hesitate to reach me!

Thank You for Reading!

If you like the story, please recommend it, it means a lot to me!

If you like the libraries, please star it.

And finally, if you want to see more stories like that, follow me at Twitter and Medium.

--

--