Decreasing headaches with frontend icons library

Katya Pavlenko
Yotpo Engineering
Published in
9 min readSep 12, 2022

Imagine you have an icon library that is used by ~50 developers among 8 frontend apps written in various frameworks. What is the best way to store, distribute, and maintain that library?

Hi, I’m Katya, a Senior Frontend Infra Engineer at Yotpo, and part of my work is to reduce pain points for our engineers and improve the developer experience.

Recently I worked on revamping our icon library, and this is the story of how we improved the icons experience for literally everyone in the company and what solution we came up with.

This article will be useful to you if you are thinking of building your own library or are tired of maintaining current solutions and looking for another approach.

This is how our library looks like now

Let’s go over basic concepts and then dive into implementation details!

Intro

What is an icon library?

Just a set of SVGs
The most common solutions are icon fonts or pure SVG files. This story is written in the wake of deprecating icon fonts and building a new icons library based on pure SVG. Why was this decision made? Long story short: we wanted more control and reliability. Here’s an article explaining the benefits.
If you are a fan of fonts, don’t stop reading! There is much more in this article that can also be adopted to using a font.

How is the icon library distributed?

Via npm package @yotpo/icons which contains only one .js file containing an icons object:

There are, however, a few optional strategies. For the beginning we went with packing all icons into one .js file.

It allows us to get any icon immediately, without sending additional requests. Ensuring that no matter how the project is built, the file will reach the final bundle without additional changes to the webpack (or other building tool) config.

The most controversial thing here is performance.

Why load such a huge file every time? You are right. But magically, after gzip, this file weighs exactly the same as an icon font. So we are not losing anything in comparison to the previous solution.

How is the icon library used?

SVG is a valid part of HTML, so it’s tempting to say that it simply needs to be injected there. But how? In modern frameworks, it’s not such a simple task. And our solution must support all frameworks, and be unified so it can be used in Angular, React, Vue, and in plain HTML.

So we are providing a web-component which would work the same way everywhere. And framework-agnostic logic of rendering the correct icon would be hidden inside of it, as behind a facade.

<yotpo-icon name="calendar"></yotpo-icon>

Right now, it does something as simple as just taking a value from the object and injecting it directly into HTML. The beauty of using facade is that we can add changes to the internal logic, without developers even noticing. This means that the moment the icons’ performance starts bothering us, it will be super easy to fix, even on a big scale. And all that developers need to do is just update a couple of npm packages.

Wrapper <yotpo-icon/> is distributed in another npm package @yotpo/components along with other reusable components (buttons, inputs, etc.). This library declares icons-lib as a peerDependency package:

"peerDependencies": {
"@yotpo/icons": "*"
}

So the consumer app needs to specify their own version of the icons library and have full control over it. And if a new icon is needed, they can explicitly install a new version of tiny@yotpo/iconswithout being afraid that any components logic would be broken.

@yotpo/icons: '^7.0.1'
@yotpo/components: '^1.2.5'

Development of web-components is also super interesting and while we are working on an article about it, you can check out our favorite open source web-components library shoelace(it also has an icon component implemented).

How is the icon library built?

The only source we have is a folder with SVG files.

To convert them into one .js file we are using svg-to-ts package with consequent ts compilation. It also provides us with nice auto-generated typewhich can be used later in the app’s code.

File pre-processing

SVG files are provided by our designers. But can we directly use them as icons without previous processing? No.

There are requirements to meet:

  • It must have no color to be able to inherit it from wrapper’s CSS and change icons color on demand
  • It must look good at any scale: it means unified size, no 3rd party fonts, embedded images
  • It must not contain XSS attacks: it means no scripts or event listeners
  • It must be as lightweight as possible to optimize loading time

An example of processing looks like this:

For this processing, we are using a nice svgo package (it’s already integrated into svg-to-ts). Our config is here. All source .svg files are stored unchanged in the repo itself and are processed altogether on every lib build.

Can we just ask our designers to prepare SVG in this manner?
Of course, yes.
Do we really think they’d send us a file with XSS?
Of course, no.

But when it comes to an automation, there is no place to rely on the human factor. Plus, we already have robots, and they should work instead of us even if the job is simple.

Moving manual work to CI

What the manual process of adding an icon looks like

It’s easy to add icons manually:

  1. Put new source .svg file(s) in the svg-filesfolder
  2. npm run build:lib
  3. npm version minor/major
  4. npm publish
  5. git push --follow-tags

It looks pretty simple, but especially because it’s a manual work without any brain power involved. It is SUPER annoying and definitely can be moved to robots.

Let’s shortcut it to:

  1. Put new source .svg file(s) in the svg-filesfolder
  2. git push

Building CI workflow

Disclaimer: we are using github actions for this solution, but it’s easily adoptable for any other CI tool.

The first thing to decide on — what should be a trigger event? In this case it’s definitely a push to the repo.

There are 2 strategies: build a library if push diff contains library-related files or build a library anyway and then decide if it worth publishing.

First is faster — we are not spending time on build (and npm i) which may not even be needed.

But it’s tricky: which files should we check for?

We are building a library first for each commit! The whole flow would look like this:

How to check if the lib should be released? It can be done by simply comparing the freshly built lib content, to the latest version released to npm.

But since we are comparing the latest version and a candidate, let’s also calculate if the release should be minor or major according to semver.

This means that if there were only new icons — it’s minor, but if any icon was removed or changed, it’s major. Following semver is important, even in such a small and sans-logic library. Because semver brings us reliability, and this whole process is about reliability.

Final CI flow

QA for icons

So cool, at this point we already have an effective and user-friendly internal product.

  • App developers can easily use icons, using facade component
  • To add a new icon to the set, we just need to add an .svg file to the folder and push it

One last milestone is left. We still have 2 people in the process: a designer and a developer. And also one robot: an icon processor. And this processor might break an icon.

Visual linting of an icon

As you may remember, we need to remove all colors to enable SVG to inherit the color of its parent. Which can lead us to a transformation like this:

All because the file had a transparent rectangle inside. Why? We don’t know, but it’s our problem now.

How can it be fixed? Apparently the robot is not capable of making such decisions. This means that someone should notice this issue and fix the source code of the icon. In a perfect world, it should be a designer, but in the real world, with the process we have, it’s gonna be a developer — one who adds the file and does the push invoking library build process.

And then the developer should fix it themself (in case it is as simple as my example) or request fixes from a designer. Both options are soooo not automated, you don’t even want to think about them!

But we still need validation. What’s the solution? Web validation tool for a designer!

So each time a new icon is being added — it can be checked without even involving a developer.

Yes we can do it! And easily. We’re already using svgo — so let’s write a simple backend that takes an icon and returns it processed, using exactly the same config as we use for lib building.

+ a couple of buttons on an already existing web page — and now a designer can check if their .svg makes it good to be used as an icon and can fix it if not.

If in the future we change how we process files — linter will also be changed automatically because it uses the same config which is used for processing. Reliable!

Automating the process completely

If a designer is already using some tool with our backend processing for an icon, but still needs a developer just to make a push — is it fair (to the developer)?

Not really. Why switch contexts to do a simple push just because the designer doesn’t have a GitHub account? Moreover we have all we need to do completely end-to-end solution for designers.

Which we did — we added one more request to our backend, which invokes a push to the repo with new icons. Thanks to our thoughtful CI architecture — it’s the only thing needed, everything else is already automated. And sprinkled the web page with some nice UI to make it the one and only place for icons management.

interface that designer uses to maintain icons library

Now we only hear about changes in icons library from our slack bot.

Though “backend invoking push to the repo” sounds simple, it also has interesting complications (for example, we did not just push with GitHub REST API). But that’s a subject for another article about Github Actions which is already being baked ;)

Conclusion

We have a working solution!

  • App developers use icons in a straightforward way in any imaginable framework, using <yotpo-icon> facade (without needing to care what’s inside)
  • Designers are 100% sure that their SVG will make a proper icon and don’t need to ping-pong with developers anymore, they own the library completely
  • Icon library developers (us) do nothing, because everything is automated already (until we want to improve something)

What else?

But wait, something is not perfect yet.

Designers work in Figma and store all assets there. It means that each new added icon needs to be taken to some external tool we built, which is okay, but still a bit annoying.

So we put a task in our jira backlog and it looks like this

Because what is a Figma plugin? It’s a typescript frontend app. But we know how to handle it!

--

--

Katya Pavlenko
Yotpo Engineering

frontend developer, love instant noodles and super simple explanations of complex things Github: https://github.com/cakeinpanic