Making a subset of Font Awesome

Font Awesome is, well, awesome - but there's no getting away from the fact that if you only want a few of the icons then it's not a lightweight package.

Looking around for something to solve this with I found Logan Graham has written a nice JavaScript package to solve this, called - appropriately enough - fontawesome-subset.

That will do the work of extracting the icons you want and repackaging them back into font files, but you still need the CSS files to use them.

It only works with Font Awesome 5.x - if you want to use 6 then it looks like you need to use the Font Awesome Pro kits, or pull up the issue on Github and have a crack at it. :-)

After a couple of times trying to deploy this on a website and forgetting to copy or run something I decided that it was probably time to invest in setting up a task runner.

Which runner?

I’m sure there are more, but the ones I’ve seen are Gulp and Grunt. I needed some way to decide which to go for, and the GitHub repos actually made that fairly simple.

Gulp

Gulp hasn’t been updated since mid-2021, the CI test is failing, and it doesn’t support Node >= 14.

Grunt

Grunt was last updated 3 days ago (at the time of writing), and has passing CI tests.

So, Grunt it is.

Demo webpage

Just to note this post isn’t going to walk you through actually using FA - that’s not the focus here. So if the classes below don’t make sense to you, head over to the FA docs.

Here’s the page:

<html>
  <head>
  </head>
  <body>
    <form>
      <label for="email">
        <i class="fas fa-envelope"></i>
        Email
      </label>
      <input type="email" id="email"/>
      <label for="password">
        <i class="fas fa-lock"></i>
        Password
      </label>
      <input type="password" id="password"/>
      <button type="submit">Click me</button>
    </form>
  </body>
</html>

Right now we don’t see any icons - we haven’t done the subsetting yet, and we don’t have any CSS files included.

Email and Password field from a webpage, no icons visible.
Before we load any CSS, no icons to be seen

We’ll use the livereloadx package for serving the content, since we don’t have to write any code to use it. We’ll add the command to the scripts section in package.json, to make it easy to run.

"scripts": {
    "serve": "node ./node_modules/.bin/livereloadx -s ."
}

How to subset the files

After all, if we can't work this out there's not much point in doing the rest. Luckily, the usage section of the README makes it fairly easy to work out.

Import the fontawesomeSubset function:

const { fontawesomeSubset } = require("fontawesome-subset");

Then we call it with two arguments. The first is a name, or array of names, for the icons that we want (without the fa- prefix). The second is where it should write the font files it's going to produce.

fontawesomeSubset([
  'envelope',
  'lock',
],
'static/webfonts');

And indeed, if we run that we can see it works, or at least produces some output. The output files are a fraction of the size of the source files, which is encouraging.

$ node fa_subset.js
$ fd . -t f static/
static/webfonts/fa-solid-900.eot
static/webfonts/fa-solid-900.svg
static/webfonts/fa-solid-900.ttf
static/webfonts/fa-solid-900.woff
static/webfonts/fa-solid-900.woff2
$ du -hs static/webfonts/
 20K	static/webfonts/

Getting Grunt setup

First let's get Grunt to the point where it runs successfully, even if it doesn’t do anything.

Grunt config can live in various places, but I’m choosing to put it in Gruntfile.js to keep it separated. The simplest config is as below:

module.exports = function (grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON("package.json")
  })

  // We'll load any plugins here.
  
  // 'default' is run if you run Grunt without arguments.
  grunt.registerTask('default', []);
}

Note the ‘default’ task being registered even though it doesn’t do anything. As well as being a good placeholder, it also means if you just run grunt with no arguments it won’t produce an error. We’ll come back to that later.

And with that, it's alive - though admittedly it doesn’t say much.

$ grunt

Done.

Copying the files

To use the web fonts, we’ll use the CSS files from the Font Awesome package. They should automatically load the webfont files that we’ve extracted. Just a reminder - this only works with Font Awesome 5.x, not 6.x.

There’s a grunt-contrib-copy package which teaches Grunt how to copy files. The first thing after installing it is to load it in the Grunt setup function.

grunt.loadNpmTasks('grunt-contrib-copy');

From the Grunt docs we can see that we need:

  • the expand option, to enable the other options
  • the flatten option, to flatten the destination results to a single level.
  • src, to list the source files
  • dest, to tell Grunt where to put the copied files.

From that we end up with a config object like this.

grunt.initConfig({
  pkg: grunt.file.readJSON("package.json"),
  copy: {
    main: {
      files: [
        {
          expand: true,
          flatten: true,
          src: [
            'node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css',
            'node_modules/@fortawesome/fontawesome-free/css/solid.min.css'
          ],
          dest: 'static/css/'
        },
      ]
    }
  }
});

We can run that, and it says it's copied two files. Let's just check it's the right two files, and they're where we expect.

$ grunt copy
Running "copy:main" (copy) task
Copied 2 files

Done.
$ fd . -t f static
static/css/fontawesome.min.css
static/css/solid.min.css

Yep, that looks good.

Running the subset task

Now let's get Grunt to actually do the subsetting. For this we'll use the registerTask() function, which provides a general purpose “run this function” task. That has the following prototype:

grunt.registerTask(name, description, function)

So, let’s try just plugging in the function we wrote earlier.

grunt.registerTask('fasubset', 'Subset the FA icons.', function() {
  fontawesomeSubset([
      'envelope',
      'lock'
    ],
    'static/webfonts');
});

And … it works. That was almost too easy!

$ rm -rf static
$ grunt fasubset
Running "fasubset" task

Done.
$ fd . -t f static
static/webfonts/fa-solid-900.eot
static/webfonts/fa-solid-900.svg
static/webfonts/fa-solid-900.ttf
static/webfonts/fa-solid-900.woff
static/webfonts/fa-solid-900.woff2

Running tasks by default

For the final bit of Grunt config, let's change the default task to run the copy and subset, so we can just run grunt in future without any arguments.

grunt.registerTask('default', ['fasubset', 'copy']);

Add the CSS files to the webpage

Now we need to start using the CSS files, otherwise nothing will happen.

<html>
  <head>
    <link rel="stylesheet" href="/static/css/fontawesome.min.css">
    <link rel="stylesheet" href="/static/css/solid.min.css">
  </head>
[...]

Check the results

Finally, let's check the webpage again - we should see that our icons have popped into existence.

Email and Password field from a webpage, with envelope and padlock icons visible.
After loading CSS, with icons visible

That's all there is to it! I have to admit I was expecting more difficulty when I started, but actually Grunt is designed nicely enough to make sense, and fontawesome-subset Just Works. I like packages like that.

How about you? Got any useful packages you'd like to share in the comments?


Subscribe to Paul Walker

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe