Creating a FontAwesome bundle with only the icons you use

FontAwesome contains thousands of icons, but you often use a very small subset of those icons. For instance, I only use 15 icons on this website. So, why do you want your visitors to download hundreds of icons they won't use. One solution is to create a custom bundle that contains only the icons you actually use. You can reduce the bundle size from a few hundred kB to a few kB (6kB on this website).

Minifying FontAwesome using JavaScript modules

If you are using FontAwesome with ES6 or Node.js you can use Font Awesome (JavaScript) SVG Core.

import { library, dom } from '@fortawesome/fontawesome-svg-core'
import { faUserAstronaut } from '@fortawesome/free-solid-svg-icons'

// We are only using the user-astronaut icon
library.add(faUserAstronaut)

// Replace any existing <i> tags with <svg> and set up a MutationObserver to
// continue doing this as the DOM changes.
dom.watch()

Then, using a tool such as Webpack that provides tree shaking to bundle your application, only the used icons will be in the final package.

Minifying FontAwesome using a custom application

In my case, I prefer generating a JavaScript file without any extra things such as the MutationObserver. Also, I would like to integrate icons from other icon sets.

First, you have to download the zip file that contains all the icons as svg files, and keep only the icons you want:

Then, you can create the C# application:

  1. Download .NET SDK: https://dotnet.microsoft.com/download

  2. Create a new application and add the needed NuGet references

    dotnet new console --name FontAwesomeBundler
    cd FontAwesomeBundler
    dotnet add package Newtonsoft.Json
  3. Replace the Program.cs file with the following content:

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Xml.Linq;
    using Newtonsoft.Json;
    
    namespace FontAwesomeBundler
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                var ns = XNamespace.Get("http://www.w3.org/2000/svg");
    
                var sb = new StringBuilder();
                sb.Append("(function(){");
    
                var icons = new Dictionary<string, Dictionary<string, string[]>>(StringComparer.Ordinal);
    
                foreach (var dir in Directory.GetDirectories(args[0]))
                {
                    var currentDict = new Dictionary<string, string[]>(StringComparer.Ordinal);
                    icons[Path.GetFileName(dir)] = currentDict;
                    foreach (var file in Directory.GetFiles(dir, "*.svg"))
                    {
                        var doc = XDocument.Load(file);
                        var className = Path.GetFileNameWithoutExtension(file);
                        var viewBox = doc.Root.Attribute("viewBox").Value;
                        if (!viewBox.StartsWith("0 0 ", StringComparison.Ordinal))
                            throw new Exception($"Expecting viewbox to start with '0 0 ' for '{className}'");
    
                        viewBox = viewBox.Substring("0 0 ".Length);
    
                        var path = doc.Root.Element(ns + "path").Attribute("d").Value;
                        var size = viewBox.Split(' ');
                        var width = int.Parse(size[0], CultureInfo.InvariantCulture);
                        var height = int.Parse(size[1], CultureInfo.InvariantCulture);
                        var ratio = width / height;
                        var ratioClass = ratio == 1f ? "16" : (ratio == 1.25f ? "20" : "14");
                        currentDict.Add(className, new[] { viewBox, path, Math.Ceiling(width / (double)height * 16d).ToString(CultureInfo.InvariantCulture) });
                    }
                }
    
                sb.Append("var icons = " + JsonConvert.SerializeObject(icons, Formatting.Indented) + ";");
    
                sb.Append(@"function i2svg() {
        var ns = 'http://www.w3.org/2000/svg';
        var groups = Object.keys(icons);
        for (var i = 0; i < groups.length; i++) {
            var groupName = groups[i];
            var iconNames = Object.keys(icons[groupName]);
            for (var j = 0; j < iconNames.length; j++) {
                var iconName = iconNames[j];
                var elements = document.querySelectorAll('i.' + groupName + '.fa-' + iconName);
                for (var k = elements.length - 1; k >= 0; k--) {
                    var iconData = icons[groupName][iconName];
                    var svg = document.createElementNS(ns, 'svg');
                    svg.setAttribute('viewBox', '0 0 ' + iconData[0]);
                    svg.setAttribute('class', 'fa');
                    svg.setAttribute('class', 'fa-' + iconData[2]);
                    svg.setAttribute('aria-hidden', 'true');
                    var path = document.createElementNS(ns, 'path');
                    path.setAttribute('d', iconData[1]);
                    path.setAttribute('fill', 'currentColor');
                    svg.appendChild(path);
    
                    elements[k].parentNode.replaceChild(svg, elements[k]);
                }
            }
        }
    }
    var raf = window.requestAnimationFrame;
    raf ? raf(function () { window.setTimeout(i2svg, 0) }) : window.addEventListener('load', i2svg);
    ");
    
                sb.Append("})()");
                File.WriteAllText(@"fontawesome-custom.js", sb.ToString());
            }
        }
    }
  4. Run the application to generate the bundle:

    dotnet run <Path to the icons folder>

You now have a custom JavaScript file that will replace the <i class="fas fa-rss"></i> tags by <svg>...</svg> tags. Finally, you need to add the following CSS rules to display correctly the icons in the page. This does not include all the rules from FontAwesome because I don't need all of them for this website. For instance, animations (fa-spin or fa-pulse) are not available, but you are free to copy them from the original stylesheet.

.fa {
  display: inline-block;
  font-size: inherit;
  height: 1em;
  overflow: visible;
  vertical-align: -.125em;
}

.fa-9 {
  width: 0.5625em;
}

.fa-12 {
  width: 0.75em;
}

.fa-14 {
  width: 0.875em;
}

.fa-16 {
  width: 1em;
}

.fa-18 {
  width: 1.125em;
}

.fa-20 {
  width: 1.25em;
}

Conclusion

The loading time of FontAwesome on this website is much faster because the bundle is much smaller, so the downloading time, parsing time, and evaluation time is cheaper. It also reduce the memory needed to display the page. It has been a very good change on this website in term of performance.

Follow me:
Enjoy this blog? Buy Me A Coffee Donate with PayPal

Leave a reply