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.
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 filesdest
, 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.
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?