How to create a fully tree shakable icon library in Angular

Kevin Kreuzer
Mar 23 · 10 min read

Nearly every SPA uses icons. Often, we use icons delivered by a component library, such as Material, or we take icons from a dedicated icon library like font-awesome. In some cases, however, the icons need to match our brand. Therefore, we need to provide a custom icon library.

If you create an icon library, performance is something to consider. You don’t want to deliver a set of 300 icons to the user if they only are going to display two icons. Right?

Previously, I wrote an article about how to create an icon library in Angular. I recommend you check out this article because it explains a lot of concepts and best practices when it comes to creating an Icon library. It will also help you better understand the things explained in this article.

Too lazy to read the article? Here’s a quick summary:

  • We should prefer SVG icons over fonts because they offer more features.
  • Since SVG icons are just code, they are bundled into our JavaScript files.
  • An icon registry is a clean way to make icons tree shakable.
  • svg-to-ts is a nice helper tool to transform SVG icons into code.

Why another article❓

Let’s start by having a look at what can be improved in the current approach?

Throughout this blog post, we are going to use those awesome Icons made by Freepik from www.flaticon.com

Different levels of tree shaking 🌲

Never heard of “fully tree shakable”? Well, that’s not surprising, it’s a term I invented here (it’s not an official term 😉). What do I mean by that?

Treeshakable icons so far

Tree shakable icons: Only the used icons end up in the resulting bundle

So far we created an icon library containing 5 icons. With a non-tree shakable library, all 5 icons result in the final bundle. With a tree shakable library, on the other side, only the used icons end up in the final bundle.

So far so good. The question now is, what happens if we use Icons inside lazy-loaded feature modules? 🤔

Lazy loading is one of the most important Angular features to boost initial start time of a web application. Lazy-loaded code ends up in a separate JavaScript bundle so that it is only loaded once it is needed. Therefore, the size of the main bundle decreases and our application loads faster.

Let’s say we have an application that shows professions. The application contains two lazy loaded routes; /artist and /cook. Each route contains a description of the profession and uses the dinosaur icons to illustrate the profession.

Professions application that uses the dinosaur icons to illustrate a profession

Since we are interested in performance its best practice to analyze our bundles using the webpack-bundle-analyzer.

All Icons get split into a separate common chunk. Lazy loaded feature modules get split into their own chunk

All of our icons get split into a separate chunk. This means when we navigate to /cook we load all icons, even though we only display the cook dinosaur icon.

In the example above, we imported all icons in lazy loaded modules. If an icon is included in an eagerly loaded module, all icons will end up in the main chunk. Having icons in the main bundle decreases the initial load time of our application.

If an icon is eagerly loaded, all icons end up in the main chunk

Wouldn’t it be great if the icons would also be split to the lazy-loaded chunks? This is what I mean by the term “fully tree shakable” icons.

☝️ Fully tree shakable are icons that are split to the chunk they are uesd in.

How to create a fully tree shakable icon library?

In case you still didn’t read the previous blog post, here is the component and the registry code as a refresher.

dinosaur-icons-registry.service.ts
dinosaur-icon.component.ts

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

If you want to enable tree shaking you need to be explicit about your intends.

We need to rethink how we convert the icons❗

To be fully tree shakable, each icon needs to end up as a single piece of JavaScript.

Sounds good, how do we get there?

The good news is, we extended the svg-to-ts generator so that it does all the heavy lifting for you. We just have to configure it accordingly. Let me show you how.

In case you haven’t installed svg-to-ts yet, just go ahead and use the following command to install it as a dev dependency npm i -D svg-to-ts.

We will configure svg-to-ts given the following file structure:

dinosaur-icons
|_ icons
|_ svg-icons
|_ artist.svg
|_ birtday.svg
|_ chef.svg
|_ sleep.svg
|_ space.svg
|_ src
|_ lib
|_ dinosaur-icon.component.ts
|_ dinosaur-icons-registry.service.ts
|_ dinosuar-icons.module.ts

svg-to-ts has multiple ways to be configured. You can either pass all the configuration arguments over the command line, add a svg-to-ts key with a configuration object to your package.json or you can add a .svg-to-tsrc file on the root level of your project.

I personally prefer to configure it by using a configuration object in my package.json.

"svg-to-ts": {
"srcFiles": [
"./projects/dinosaur-icons/icons/**/*.svg"
],
"outputDirectory": "./projects/dinosaur-icons/icons",
"interfaceName": "DinosaurIcon",
"typeName": "dinosaurIcon",
"prefix": "dinosaurIcon",
"optimizeForLazyLoading": true,
"modelFileName": "dinosaur-icon.model",
"additionalModelFile": "./projects/dinosaur-icons/src/lib",
"compileSources": true
}

Let’s take a quick walk through all those properties and their meaning.

  • srcFiles: Regex that matches the source files (SVG icons).
  • outputDirectory: the directory where we want to create the icons.
  • interfaceName: Name of the generated interface for the icons.
  • typeName: Name of the generated type for the icons.
  • prefix: Prefix of the icons and the generated icon files.
  • optimizeForLazyLoading: Flag to indicate if we want to optimize our library for lazy loading (making it fully tree shakable)
  • modelFileName: Name of the generated model file (File that contains the interface and the types.
  • additionalModelFile: If we want to create an additional model file. This file is useful to improve the typings.
  • compileSources: Flag to control if the SVG‘s should be compiled by the TypeScript compiler or not. If set to false svg-to-ts will output .ts files. If set to true svg-to-ts will output .d.ts files and .js files.

After we configured svg-to-ts we can go ahead and add a generate-icons script to our package.json.

"generate-icons": "svg-to-ts"

This script runs svg-to-ts with the configuration of our package.json and generates the following files.

Fils generated by svg-to-ts

All the icons are optimized, transformed to TypeScript and transpiled to JavaScript files. Each icon becomes a JavaScript file. Furthermore, svg-to-ts also generates the correct TypeScript declaration files and a dinosaur-icon.model.d.ts file to improve typings.

Additionally, we also specify the path for an additionalModelFile. This file is generated next to our DinosaurIconComponent and the DinosaurIconRegistry. Generating this file a second time is necessary because we want to have type safety and the icon file is currently not included in our library build.

Including the generated icons

npm i -D cpx

Once installed, we add the following post-build script in our package.json.

"postbuild": "cpx 'projects/dinosaur-icons/icons/**/*.{d.ts,js}' dist/dinosaur-icons/icons",

This script copies all the d.ts and js files to the dist/dinosaur-icons/icons folder after our library build which results in the following dist folder.

That’s it, at this point we created a fully tree shakable icon library.

Usage of our icon library

Import the DinosaurIconsModule and add the dinosaurIconArtist icon to the DinosaurIconsRegistry

We import the DinosaurIconsModule from dinosaur-icons and add it to the imports array of our ArtistModule. With this, we can now use the dinosaur-icon component in our templates.

To display the artist icon we first need to import it from dinosaur-icons/icons (our icons folder) and add it to the DinosaurIconsRegistry.

At this point we are ready, to use the artist icon in our template.

Use the artist icon in our template

The cool thing here is that due to the typings generated by svg-to-ts, modern IDE’s give us a suggestion of the possible icon names and complain if we use a wrong icon name.

Cool, but how does our bundle look like? Are our icons really fully tree shakable. Yes, they are!

Icons split on lazy-loaded modules

We can see that the icons do not land up in a common chunk, nor in the main bundle. They now end up in the bundle they are really used.

Here’s another image of the bundle analyzer that additionally shows the main bundle.

Fully tree shakable icons decrease the sice of our main bundle

Even if an icon is included in an eagerly loaded module, the rest of the icons is bundled in a lazy loaded chunk. If you have a huge icon library this decreases the size of your main bundle and improves initial loading time.

svg-to-ts gives you even more power 🦍

svg-to-ts is highly configurable and therefore very flexible. It’s up to you to decide how you want to build your icon library.

Maybe, simple tree shaking is enough for your use case, then set optimizeForLazyLoading to false. Maybe you don’t want to compile the icons because you are only interested in the TypeScript files, then set compileSources to false. Find the correct configuration for your use case.

In our current project as an example, the same icon library is used by multiple component libraries. Therefore we decided to extract the icons to a separate package. This has the advantage that the icons can be used by multiple registries and we only have to make one release if the icons change.

In this approach, we only generated the TypeScript sources and compiled them ourselves as part of our build process.

Conclusion

svg-to-ts is a very helpful tool that does all the heavy lifting for you when you want to generate your own icon library. It can be used with multiple frameworks. It accepts various configurations and therefore supports multiple ways of building an icon library.

🧞‍ 🙏 If you liked this post, share it and give some claps👏🏻 by clicking multiple times on the clap button on the left side.

Feel free to check out some of my other articles about front-end development.

Angular In Depth

The place where advanced Angular concepts are explained

Kevin Kreuzer

Written by

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

Angular In Depth

The place where advanced Angular concepts are explained

More From Medium

More from Angular In Depth

More from Angular In Depth

Angular Bad Practices: Revisited

More from Angular In Depth

597

More from Angular In Depth

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade