How to build your own tree shakable SVG icons library in less than 30 minutes!

How to create your own FontAwesome. Ship SVG icons optimized for tree shakers and splittable across multiple bundles.

Don’t let the title fool you; building an icon library in a tree-shakable way is hard, very hard. If you make it from scratch, it’s impossible to do it in the mentioned time. But, here’s the good news. We already did the hard work for you.

I took everything I learned while building icon libraries for big companies and wrote a whole open-source tool suite so that you, with the help of this blog post, can build your own performant SVG icon library in less than 30 minutes. Sounds good? Let’s get started.

🤫 Everything described in this blog post is also available as a four part video tutorial on Youtube.

Let’s learn from the best — How does FontAwesome deliver SVG icons? 🤔

FontAwesome is probably one of the most popular if not the most popular icon library on the market. Of course, only until you are done building your icon library. 😉 Font Awesome has tons of consumers and production-proofed concepts. Let’s have a look at how they deliver icons.

FontAwesome provides a library that is called @fortawesome/free-solid-svg-icons. A library that contains all SVG icons. To render those icons, we need another library named @fortawesome/angular-fontawesome.

Let’s explore how we can use those libraries in Angular and how those libraries play together.

We import the FontAwesomeModule from angular-fontawesome. By adding this module to the imports array we get access to the FaIconComponent which we will explore in the next step.

Additionally to the module, we also import a FaIconLibrary service. This service is a registry that will hold icons. In our example, we are going to use three icons (faGithub, faCheck, faPlus) which we import from the free-solid-svg-icons package.

I mentioned that you could think of the FaIconLibrary as sort of a registry. To use our icons, we will register/add them to the FaIconLibrary inside of our constructor. Don’t worry if this doesn’t make sense yet. We will visualize this concept later.

To render the actual SVG icons, we use the fa-icon component in our template and pass the name of the icon to it.

Oh boy. A registry, two libraries, why not just render the SVG icons directly? In the end, an SVG icon is just code. Well, yes, SVG icons are only code. Yes, we could render them directly to the DOM, should we? Probably not. At least not in an enterprise application. Why? Because of performance.

Shake of unused icons

There are different types of performance. In frontend development — especially in single-page applications, loading performance is very important. How long does it take to download our JavaScript file? This is not just a technical experiment. Time is money!

🧑‍🎓 Pages that load within two seconds have an average bounce rate of 9%, while pages that take five seconds to load have a bounce rate of 38%.

The initial load time is coupled with the size of our JavaScript bundle. In other words: to improve the decrease of the initial load, we also need to decrease the JavaScript bundle size.

The most common techniques to achieve this are tree-shaking, aka dead code elimination, and lazy loading. We are going to build our icons in a way that optimization tools and bundlers like Webpack can use both techniques on our icons. In other words.

The goal is that only the used icons get shipped to the browser and not all. Furthermore, we want to make our icons splittable. This means each icon should end up in the chunk it is used. If it is used in a lazy loaded chunk, it will only be included there.

Build like font-awesome

In the previous section, we saw that font-awesome provides an SVG icon library and an Angular adapter that contains a reusable icon component and a registry.

We are going to use the same concept. But of course, we are going to build it with our open-source tool suite. Let’s visualize this concept and add the various open-source tools to the graphic.

We have 3 central components: the single-page application, the icon-library, and an Angular adapter. The SPA is a sort of glue that, in the end, pulls everything together. The SPAs imports the icons it wants to use, registers them, and then uses the provided wrapper component to visualize it.

In our case, it will be an Angular SPA that is generated with the Angular CLI. But it could also be a single page application that is written in Vue or React. Of course, in this case also the adapter would need to be written in React or Vue.

Enough theory, let’s build our own FontAwesome!

Building the icon library 👷

Let’s start with the icon library. Before we focus on the engineering part, we need a set of SVG icons. Often a designer will provide those icons, or maybe you will even create them on your own. However, for the sake of this blog, we are going to use an existing set of SVG icons.

Once we downloaded our SVG icon set, it’s time to fork the svg-icon-library-starter project on GitHub and follow its “Getting started” instructions in the README.

Once forked, we can customize the project. To do so we run a full-text search on our repository and search for REPLACE_ME occurrences.

The first two adjustments are straight forward. First, we need to adjust the name in the package.json then the title in the h1 tag in the index.html. In our case, we change it to “Greek mythology icons starter.”

The other adjustments are in the .svg-to-tsrc file. As illustrated in the graphic, the starter uses one of our open-source tools called svg-to-ts. The .svg-to-tsrc file contains configurations for svg-to-ts.

The configurations are the ones that we feel are best for delivering tree-shakable icons. However, svg-to-ts supports multiple ways of delivering icons. If you want to know more I recommend you to check out the svg-to-ts docs.

Even though svg-to-ts is already preconfigured, there are still a bunch of customizations left. Let’s change the following properties

  • interfaceName to GMIcons
  • type to gmIcons
  • prefix to gmIcons
  • modelFilename to gm-icons.model.ts

That’s everything that is needed in terms of customization of the starter project. The last step that is missing is to replace the README.md in the svg-icons folder with our raw SVG icons.

Believe me or not, but that’s all we needed. Now it's time to see our icons in action. Let’s run the built-in showcase by typing npm run serve.

This showcase displays all of our svg-icons and comes with an integrated filter function. It could be deployed somewhere so that stakeholders can have a look at our icons.

Now it's time to build and publish our library. To do so, we can type npm run build then change into the dist/icons directory and publish to npm by running npm publish.

That’s it for the first piece. Let’s move on to the Angular adapter.

If you like this blog post and want to get updated about new cool things that happen in modern frontend development — follow me on Twitter.

Building the Angular adapter

At this point, our icons can be imported, but we haven’t yet provided a way of actually rendering them to the page. Don’t repeat yourself is one of the holy credos in software development. And that’s exactly what we are going to do here by providing a reusable component and registry.

The component and the registry can either be implemented in a new library or it can also be part of an existing component library.

This might not sound very easy. But again, we got you covered. We are going to take advantage of some awesome schematics projects.

Let’s start by creating a fresh Angular library called ng-greek-mythology-icons. To do so, we are going to use the Angular CLI.

We explicitly set the --create-application flag to false. Without this flag, the ng new command would generate an application with the same name as the folder, not what we want. We want to create a library, not an application.

The result of the execution above is an empty repository. We will now change to this directory and generate our actual library into it.

We now end up with a library project which is set up under projects/ng-greek-mythology-icons. Next, we need to build the icon component and the icon registry. Here again, we are going to use some nice schematics that will do the job for us. Let’s go ahead and install @angular-extensions/svg-icons-builder as a dev dependency.

Once installed, we can use run the ng-generate-icon-lib schematics.

This command walks us through a setup wizard that asks a bunch of questions we need to answer.

⚠️ You must enter the correct icon library name and the same type and interface as you previously in the .svg-to-tsrc configuration. Additionally, you can also enter a module name and a default size for your icons. We also recommend you let the schematics auto-install the previously created icon library.

The schematics create the following code for us:

  • gm-icon-registry.svervice.ts / gm-icon-registry.service.spec.ts
  • gm-icon.component.ts / gm-icon.component.spec.t
  • gm-icon.module.ts

Let’s take a closer look at the GmIconRegistry.

Technically, the registry is just a service with a Map. The map's job is to store the icons. To hold an icon, the name property is used as a key.

Therefore, each icon needs to be added to the map by calling the registerIcons function before it can be used. The getIcon function accepts a name as a parameter and uses it to access the registry's earlier registered icons.

Thx to the types svg-to-ts creates we can type the iconName. Thanks to this typing we get nice IntelliSense in the form of autocompletion as well as build time support.

Let’s now take a closer look at the caller of the getIcon function, the gm-icon.component.ts.

The idea here is that you add it to the registry once you want to use an icon. Then, you pass the name of the icon to the GmIconsComponent which then uses the registry to retrieve the icons data and to append it as an SVG element.

The last thing left is to adjust the public-api.ts and to export our freshly generated module, component, and service.

☝️ The export statements look different if you take advantage of subentries.

That’s it. The only thing left is to build and publish our Angular wrapper. Let’s put everything together by implementing our SPA.

Put everything together — let’s build our SPA

We will build a SPA that displays information about the following characters: Hades, Minotaur, and Achilles. We have a lazy-loaded module and a route for every character. Each character displays an icon, a title, and a description inside a card.

We are starting with an Angular application with a side menu, the navigation, and 3 lazy loaded modules (hades, minotaur, and Achilles). Each side menu navigates on click to one of the lazy loaded modules. The only thing missing at this point are the icons.

Pro tipp: Lazy loaded feature module can easily be created with the following CLI command. ng g m features/hades --route hades --module app.module.ts

To add the icons to our nice application, we first need to install the Angular wrapper and the icons library.

Once installed, we are ready to use our icons. In the hades module, we will use the hades icon, in the minotaur module the minotaur icon, and in the Achilles module, well, you might guess the Achilles icon.

Let’s start by adding the icon to the Hades module. We already have a lazy-loaded route and the component setup. Remember the overall idea illustrated in the picture above? To display an icon, there are three steps we need to perform:

  1. Import the icon.
  2. Register the icon to the Registry.
  3. Import the Module and use the component to render the icon.

Stage one and two can be done in the HadesModule file.

First, we inject the GmIconsRegistry from our Angular adapter (ng-greek-mythology-icons) inside the constructor. Once we got a hold of the registry, we register our gmIconsHades icon.

Thx to the generated type and the prefix we can type “gmIcons” to let our IDE display a complete list of all of our icons.

Once the icon is registered, it’s time to render it. To get access to the gm-icon component, we first need to import the GmIconsModule and add it to the imports array of our HadesModule.

We are now ready to use the <gm-icon> component in our template and pass down the name of the icon we want to render as the value of the name input.

The cool thing here is that we don’t have to guess the icon name. Thx to the types generated by svg-to-ts and the schematics, we get very nice autocompletion with all the available icon names.

We only added the icon for the Hades module. Adding the icons to the other modules requires the same steps.

A look under the hood

Throughout this blog post, we talked a lot about performance and tree shaking. Reducing the bundle size is important. It’s actually the reason behind the motivation for all those tools and this blogpost. Probably also the reason why you are reading these lines right now. If we didn’t care about performance, we would drop our icons into code, and that’s it.

But we went down the hard road. Now it’s time to harvest the fruits. Let’s analyze our bundle.

To analyze the generated bundles we use a very nice tool called the webpack-bundle-analyzer which allows us to visualize size of webpack output files with an interactive zoomable treemap.

The main bundle is around 200 KB in size. Additionally, we have the polyfills, runtime, and common bundles. And, of course, our three lazy-loaded bundles hades, Achilles, and Minotaur. But where are our icons?

To find our icons, we can enter gmIcon in the search field and voila, all of our icons get highlighted in red.

Wow. 🤩 Do we only get the used icons and not all icons provided by the lib? Furthermore, the main bundle doesn’t contain an icon since our icons are only used in lazy loaded modules. The icons end up in the exact chunk that they are used in. Let’s zoom in on the lazy-loaded chunks and take a closer look.

We can clearly see that each chunk gets its own icon. Icons are not only tree shaken but are also split across lazy-loaded chunks.

Summary

Congrats! You made it! At this point, you possess all the necessary tools and knowledge for building your own icon library.

If you enjoyed my work and this blog post it would be very appreciated if you support my work in form of claps or stars. Also, feel free to share this post so that more people can benefit from it.

Also, feel free to check out some of my other articles about frontend development and software engineering.

Passionate freelance frontend engineer. ❤️ Always eager to learn, share and expand knowledge.

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store