Font Awesome 5 has a new tree shaking feature that works with webpack's tree shaking. This is really helpful so you don't have to download one giant JavaScript file with a bunch of SVG icon definitions in it like:
<script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
Which gets you file sizes like below, where all.js is 657 KB (278 KB gzipped) and index.js is 88 bytes.
Instead, you can limit the file size to only the icons you use in your project through ES2015+ module imports and get file sizes like this, where all.js is gone and index.js is now 33.3 KB gzipped. We just saved 600+ KB (or about 250 KB gzipped)!
The Font Awesome team wrote some nice documentation on how to do this, but I didn't need to reference the shakable modules to get the smaller file size benefits. Here's how I got it working with Font Awesome 5 and webpack 3.
What worked
This is the HTML for the index.html test page.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Tree shaking with FontAwesome and webpack</title>
<link href="https://volaresoftware.com/blog/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="text-center" style="font-size: 24px; margin-top: 30px;">
<div class="row">
<div class="col-xs-2">
<div>
<I class="far fa-calendar-alt"></I>
</div>
Calendar
</div>
<div class="col-xs-2">
<div>
<I class="far fa-envelope"></I>
</div>
Email
</div>
<div class="col-xs-2">
<div>
<I class="fas fa-arrow-up"></I>
</div>
Arrow Up
</div>
</div>
</div>
<script src="/dist/lib.js"></script>
<script src="/dist/index.js"></script>
</body>
</html>
And this is index.js.
import fontawesome from "@fortawesome/fontawesome";
import farCalendar from "@fortawesome/fontawesome-free-regular/faCalendarAlt";
import farEnvelope from "@fortawesome/fontawesome-free-regular/faEnvelope";
import fasArrowUp from "@fortawesome/fontawesome-free-solid/faArrowUp";
fontawesome.library.add(farCalendar, farEnvelope, fasArrowUp);
In the markup, I'm pulling in lib.js, which webpack will figure out from the commons chunk (see below about what that is), and index.js, whose code is nothing but some icon imports.
In Font Awesome 5, you can now import a single icon, multiple icons, a full set of icons (the solid, regular, light, or brand sets), or every icon they make (free or pro). If you want the tree shaking and small file size benefit, you have to import the Font Awesome library and add your newly imported icons to the library before they can be used, like I've done above.
Here is the webpack.config.js file.
const webpack = require("webpack");
const path = require("path");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = () => {
return {
entry: {
index: "index.js"
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
publicPath: "/dist"
},
devtool: "source-map",
plugins: [
new CleanWebpackPlugin(["dist"]),
new webpack.optimize.CommonsChunkPlugin({
name: "lib"
}),
new UglifyJsPlugin({
sourceMap: true,
uglifyOptions: {
dead_code: true
}
})
],
resolve: {
extensions: [
".js"
],
modules: [
path.resolve(__dirname, "scripts"),
"node_modules"
]
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["env"],
babelrc: false,
cacheDirectory: true,
plugins: [
"transform-runtime"]
}
}
}
]
}
};
};
If you're used to seeing webpack.config files, there isn't anything too novel going on. If you aren't, here's the gist: There is one application entry point, index.js. Files will be output to the /dist folder. Source maps are turned on. We clean the /dist folder before a build. We have a commons chunk called lib.js, which means webpack figures out code shared across modules that can be extracted into a common file. We are minifying with UglifyJS and setting its source map flags to true also. We have the UglifyJS dead code (tree shaking) flag set to true, but I get the same results with or without that setting turned on. The resolve section tells webpack where to look for files, and the module rule we have set is to use Babel to load *.js files unless the file comes from the node_modules folder (so use Babel's loader for our projects' scripts).
I'm pretty happy with this, and for a project with a handful or even several handfuls of icons, it probably makes sense to import just the ones your project uses. One option is to make a selectedIcons.js file, set up all your imports and Font Awesome library additions there, and import selectedIcons.js in your single entry or for pages with icons if you have a multiple entries project.
What didn't work
The Font Awesome documentation describes some special modules you can set up through webpack.config aliases, and when you import from those files, you get tree shaking.
I changed the webpack.config.js resolve section to this.
resolve: {
extensions: [
".js"
],
modules: [
path.resolve(__dirname, "scripts"),
"node_modules"
],
alias: {
"@fortawesome/fontawesome-free-solid$": "@fortawesome/fontawesome-free-solid/shakable.es.js",
"@fortawesome/fontawesome-free-regular$": "@fortawesome/fontawesome-free-regular/shakable.es.js"
}
},
Then I changed index.js to import with a different syntax.
import fontawesome from "@fortawesome/fontawesome";
import { faCalendarAlt, faEnvelope } from "@fortawesome/fontawesome-free-regular";
import faArrowUp from "@fortawesome/fontawesome-free-solid";
fontawesome.library.add(faCalendarAlt, faEnvelope, faArrowUp);
Then I re-built the code. The page still works, but the index.js file got waaaay bigger.
I don't know if I missed something in my webpack.config.js, or I've imported with the wrong syntax, or I've referenced the special shakable files incorrectly, but I couldn't get it working. If you see my mistake, please comment below and I'll update the post.
Either way, it works to import and add individual icons, and you get smaller files. All the code for this post can be found on GitHub.