Cover photo by Harpal Singh on Unsplash
You have a set of SVG icons in your React project. And you want to separate them out from your current codebase, make them a standalone library so you can reuse these icons later in another projects. This tutorial of mine can help you easily create your own icon library and automatically publish it to the npm.
Fact: I’ve learnt this from watching tailwindlabs/heroicons
TLDR: If you want to skip this post and want to check the final work instead, please click here react-icon-boilerplate. Feel free to clone my repo and create you own lib.
Setup
First, you create an empty folder and initialize a new package.json
file.
mkdir react-icons-boilerplate && cd react-icons-boilerplate
yarn init -y
yarn add -D svgo rimraf
We will use svgo to optimize our SVG icon files since:
SVG files, especially those exported from various editors, usually contain a lot of redundant and useless information. This can include editor metadata, comments, hidden elements, default or non-optimal values and other stuff that can be safely removed or converted without affecting the SVG rendering result.
You create a raw
folder which contains all your SVG icon files that need to be optimized.
mkdir raw
This is my SVG sample file, I will place it in the raw folder raw/plus-outline.svg
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>CC3942F2-90B2-4E94-AADC-715CECF64617</title>
<defs>
<rect id="path-1" x="0" y="0" width="24" height="24"></rect>
</defs>
<g id="200720" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="TP.io---Documentation---Icons" transform="translate(-444.000000, -181.000000)">
<g id="Small---24-x-24px" transform="translate(208.000000, 141.000000)">
<g id="Icons/Guideline-Preview" transform="translate(176.000000, 0.000000)">
<g id="Add" transform="translate(60.000000, 40.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill-opacity="0" fill="#FFFFFF" xlink:href="#path-1"></use>
<path d="M12,2.25 C17.3847763,2.25 21.75,6.61522369 21.75,12 C21.75,17.3847763 17.3847763,21.75 12,21.75 C6.61522369,21.75 2.25,17.3847763 2.25,12 C2.25,6.61522369 6.61522369,2.25 12,2.25 Z M12,3.75 C7.44365081,3.75 3.75,7.44365081 3.75,12 C3.75,16.5563492 7.44365081,20.25 12,20.25 C16.5563492,20.25 20.25,16.5563492 20.25,12 C20.25,7.44365081 16.5563492,3.75 12,3.75 Z M12.75,7.46052632 L12.75,11.249 L16.5394737,11.25 L16.5394737,12.75 L12.75,12.749 L12.75,16.5394737 L11.25,16.5394737 L11.25,12.749 L7.46052632,12.75 L7.46052632,11.25 L11.25,11.249 L11.25,7.46052632 L12.75,7.46052632 Z" id="Combined-Shape" fill="#00497A" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
It does looks really long and noisy. Let’s optimize it!
Optimize SVG files
To easily run svgo
command with some options, you add a script into the package.json
like below. Every time you run the command, it will re-create a folder named optimized
which contains all icons that are optimized from the raw
folder.
{
"name": "react-icons-boilerplate",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"optimize": "rimraf ./optimized & svgo -q -p 8 -f ./raw -o ./optimized"
},
"devDependencies": {
"rimraf": "^3.0.2",
"svgo": "^2.6.1"
}
}
You can run yarn optimize
to see the result. Also, you need to have a svgo configuration file in order to add as many plugins as possible that help us to clear all the redundant data. Here is my svgo.config.js
for example:
module.exports = {
multipass: true,
js2svg: {
indent: 2,
pretty: true,
},
plugins: [
{ name: 'preset-default' },
'sortAttrs',
'removeScriptElement',
'removeDimensions',
'removeScriptElement',
'removeDimensions',
],
};
Before come up with these plugins, I tried to mess around on this website SVGOMG - SVGO’s Missing GUI
This is my optimized result file.
Build React icons
We need some packages in order to transform our SVG files into React SVG components and then convert JSX syntax into CJS and ESM module format. We use babel and svgr.
yarn add -D @babel/core @babel/preset-react @svgr/cli @svgr/core camelcase terser
You get this build script from here: script/build.js. Basically, this script read the optimize
folder, get all icons and convert them into JSX by using svgr
and then transform the React code into CJS and ESM module format by babel
.
The original icon filename will become the component’s name in CamelCase plus suffix Icon
. For example with plus-outline.svg
file we will have component name PlusOutlineIcon
.
The package supports for Typescript by generate a declare file .d.ts
for each icons.
This is the usage of the icon component when the packages is installed and used by other project
import { PlusOutlineIcon } from 'react-icons-boilerplate'
Since this build script I created to fit my icons set so these line are little special, but you can change them to fit yours icons set.
// line 12 to 23
const svgReactContent = await svgr(
content,
{
icon: false,
replaceAttrValues: { '#00497A': "{props.color || '#00497A'}" },
svgProps: {
width: 24,
height: 24,
},
},
{ componentName }
);
Now, we add this build script to our package.json
...
"scripts": {
"optimize": "rimraf ./optimized & svgo -q -p 8 -f ./raw -o ./optimized",
"build": "yarn optimize && node scripts/build.js"
},
...
If you run yarn build
you will have a dist
folder as the result. You see something similar to this
And don’t forget to add these lines in your package.json
before publishing it.
{
// ...
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"files": [
"dist"
],
// ...
}
Git
Now you can push all your work to your GitHub repository. I won’t go through this since I assume we all know how to do it.
Publish
When you have the final dist
folder as the result now you can publish this to npm.
It can be easily done by login into the npm account via NPM CLI npm login
and run the npm publish --access public
after that. But I want to leverage GitHub Actions and atlassian/changesets to do this automatically for me.
changesets
To install and init changesets
:
yarn add -D @changesets/cli @changesets/changelog-github
yarn changeset init
You will have .changeset
folder and its configuration file config.json
inside. Here is my config:
{
"$schema": "https://unpkg.com/@changesets/config@1.6.1/schema.json",
"changelog": [
"@changesets/changelog-github",
{ "repo": "mikunpham/react-icon-example" }
],
"commit": false,
"linked": [],
"access": "restrict",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
If you want to publish your package as a public package, you have to change the “access” property from restricted to public.
For the first release, you have to run yarn changeset
. It will ask what kind of Semantic Versioning you want to bump your package and write a short summary about it. It will create something like this and you now can commit this file to your repository.
GitHub Release Action
changesets
has a very detailed instruction to implement their action into your GitHub workflows here https://github.com/changesets/action
Or you can get my release.yml
here release.yml.
Now, commit everything and push onto Github.
If everything is OK, a github-action
bot will create a PR just like this and wait for you to merge.
After merging, a release will be created
And go check your npm now.
What’s next
From now, whenever you have new icons, do these following step.
- Add them to the
raw
folder. yarn build
-
yarn changeset
, select major/minor/patch bump and write summary. - Commit
- …
- Profit!
Final
A very long long post huh? BUT it just takes you 30’ max to get everything up and run for the first time and mostly 2-3’ to add new icons and release new version after that.
Thank you all the contributors of
babel
,svgo
,svgr
andchangesets
for making our dev life easier than ever.
Top comments (6)
if you are getting error
rimraf Error: invalid rimraf options.
inbuild.js
then change theto this
here is the documentation to it link
Hi. Thanks for sharing it. @quanpham
I followed your guide and built my own library.
But now I encounter a problem: I export my own icon from Figma to svg, then use this svg file to make component. But I cannot apply other color to the result Icon component.
Can you tell me which tool that you use to create the svg file in the boilerplate?
Thank you.
Hey @minhthangtkqn Thank you for reading through this.
The SVGs that I used were exported from Invision which made by our designers.
In your case, I think you can modify the build script a bit to fit your icon set.
Link here
Mine icons always contain color
#00497A
which is the main color of the set. I replaced it withprops.color
so it will let me customize the color later.If your icon set is way more complex than mine, then you can read SVGR document to see more detail configurations to apply.
It's cool, man! thanks for sharing it.
hey, I have found fork here: github.com/doubleppereira/react-ic...