Hacking the FontAwesome Library with Nextjs & TypeScript for Custom icon support
The aim of this article is to outline how to directly incorporate custom Fontawesome icons into your Next.js TypeScript project. For a solid introduction on setting up Fontawesome 5 with Next.js and TypeScript please see Vuong Dang’s recent post.
2. A quick look under the hood
Create a lib directory in the root of your Next.js project. Then, create a fas-custom-integration.ts file where the magic will unfold. There is no tsx returned in the actual customization file itself which is why the library folder serves as its perfect home.
mkdir lib && cd lib && touch fas-custom-integration.ts && cd ..
Head to the @fortawesome package in node_modules. Then, open
fontawesome-svg-core/index.d.ts to inspect the contents of the declaration file:
This is it, the librarians lair 📚. Examine the first two lines of this file. The very same Interfaces imported are exported immediately thereafter. What’s important to note when viewing declaration files like this is where Interfaces of potential utility such as IconDefinition and IconLookup reside. So let’s head on over to
fontawesome-common-types/index.d.ts and view the contents of its declaration file.
3. Structure dictates function
Interfaces serve to describe the “shape” that values have in TypeScript. Personally, I prefer to think of this shape as its structure. If you’re familiar with biology you may recall that “structure dictates function” is known as the Central Dogma of Life. This thinking can be carried over to Interface usage in TypeScript; interface structure introduces strict type requirements which breathes life into the code we write. It’s do or die. Right, back to the hacking. With the contents of
fontawesome-common-types/index.d.ts before us, the key to executing custom icon integration becomes increasingly clear.
4. Dissecting Interfaces of utility
Since IconDefinition and IconLookup are of immediate utility for custom icon incorporation let’s break each down each individually.
This Interface describes two values,
iconName. The prefix value is a string corresponding to "faX" as defined by the IconPrefix type above (where X=b, d, l, r, or s). The free version of Fontawesome supports "fab" and "fas" prefixes which denote brand and solid icon styles, respectively. Then there's
iconName which is defined by the IconName type. This type describes an array of strings totaling out to 7,854 icon names in length.
Note that the IconDefinition Interface extends the IconLookup interface. This implies that the shape described by the IconLookup interface is extended, or copied, to the IconDefinition Interface. So, while it is evident that the shape of an icon is described by an array of five scalar values, it may not be as straightforward that the prefix and iconName values described by the IconLookup Interface will be prepended to the structure of our derived icon definition. IconDefinition extending IconLookup implies that our custom definition should look as follows:
With that out of the way, let’s break down the five scalar values involved in defining the shape of an icon. Width and Height values derived from an SVG’s viewBox comprise the first two scalar values required when defining an icon. The viewBox of an SVG defines the position and dimension of a user viewport. There are always four numerical values in a viewBox which are invariably ordered as follows:
viewBox structureThe third value involved in defining an icon corresponds to ligatures which are not important for our purposes. Read more about ligatures here. The fourth value of an icon denotes the “fill” of the SVG, or fillColor, as indicated by the presence of a hex-value. Lastly, the icon path(s) are ordered sequentially on a single line; there is a single space denoting row breaks within a path. If an SVG contains more than one path (d-value) a single space is used to denote the end of one path and the beginning of the next. This value is always confined to a single line of code (horizontal scrolling needs love too).
5. Translating theory to practice
With a plan of attack in mind, let’s get to it! Out of the node_modules and back to the
fas-custom-integration.ts file in the lib directory we go. To prime the file for custom icon integration, import the library and the previously dissected Interfaces of utility:
Since a real example requires a real SVG to derive values for our icon definition from, an SVG for the Vercel icon▲ is provided below (formerly ZEIT).
You might be asking yourself “Are you shamelessly promoting your favorite deployment platform?”
For those interested in utilizing a method that produces consistently formatted SVGs, save the SVG snippet above in a .svg file. Then, navigate to Figma, enter a workspace, and press Ctrl+Shift+k (or Cmnd+Shift+k for MacOS users out there). This opens your local filesystem allowing you to import the SVG as an image into the workspace. Then, right-click the newly added SVG image, hover over “copy”, and select “copy as SVG”. Paste the copied contents into your text editor and voila! The SVG from above is refactored as follows:
With a standardized SVG obtained via Figma, let the excising begin. Your custom icon in ./lib/fas-custom-integration.ts should resemble the following:
Now, pass the
faCustomVercelIcon into the library
But wait, a TSLint error?! Not to worry, this is easily rectified. Navigate back to the @fortawesome package in node_modules and open
fontawesome-common-types/index.d.ts once more. Simply add "vercel-icon" to the beginning of the IconName type:
Looking good! The IconName type is now 7,855 icons strong. While we’re here, let’s tweak one additional type for the sake of clarity. At the very top of the index.d.ts file, add “fac” to the IconPrefix type:
The IconPrefix type denotes custom styled icons, or conquered, or whatever your heart desires. As long as you adhere to the faX notation for IconPrefix, Fontawesome is happy. With the TSLint error resolved, back to the
fas-custom-integration.ts file in the lib directory we go. First, update the prefix to "fac". See? The library is more malleable than one might anticipate after all. There is one last modification to make but thankfully it does not involve returning to node_modules for a third time. Utilizing the
extends property of TypeScript interfaces, the file should resemble the following:
While it is best practice to define all custom icons in one file, the
CustomIconConstruct Interface that extends IconDefinition & IconLookup can now be imported throughout your project without having to go through the hastle of importing the two Interfaces of utility once more. So, how does this look in practice?
6. Importing to a tsx file to run locally
To test your custom icon out, open a file that ultimately renders to a pages directory file and import the following:
The following snippet is from a project that’s currently under construction using Next, TypeScript, and Tailwindcss as frameworks.
If you aren’t familiar with tailwindcss don’t worry about the
className inline-styling above. That said, do note how the
faCustomVercelIcon was received as props by the
<FontAwesomeIcon /> JSX.Element. Success!
7. Priming for production
In order for modifications made in node_modules to successfully deploy to a production environment, the following package(s) must be installed:
yarn add patch-package postinstall-postinstall
npm i patch-package
Then, execute the following command:
npx patch-package @fortawesome/fontawesome-common-types
This generates a
patches folder in your directory which houses a file outlining local updates made to the targeted package. In our case, it's the library in which we added "vercel-icon" to type
IconName and "fac" to type
IconPrefix. Then, add the following postinstall script in package.json:
The postinstall script persists local changes made to node_modules even when packages are added, removed, or updated.
Update: postinstall-postinstall is only necessary to install for yarn. But why? While the postinstall script does run after `yarn` and `yarn add <package>`, it does not run after `yarn remove <package>`. Therefore, running `yarn remove <package>` in the absence of postinstall-postinstall renders the .patch file containing module modifications ineffective since the postinstall script isn’t executed (which I just learned firsthand). Fortunately, with postinstall-postinstall installed, the “postinstall” script is executed after running `yarn remove <package>`. However, the “postinstall” script will now be executed twice for `yarn` and `yarn add <package>` which implies that the “postinstall” script itself must be idempotent in nature (its value remaining unchanged when multiplied or otherwise operated on). End Update. The patch file generated after executing the aforementioned npx command should resemble the following:
In summary, this process can be performed with any SVG file. That said, I do recommend utilizing Figma (or a similar tool) to standardize SVGs before excising the essential bits required to define custom icons. This concludes my first ever tech post. Thanks for following along and feel free to drop any questions/comments/concerns in the comments below. Happy coding!
Originally published at https://dev.to on August 19, 2020.