Create your own React icons library and publish to NPM automatically
Tags: react, svg, icons, npm
Published: 2021-09-16 18:33:16

---
title: Create your own React icons library and publish to NPM automatically
published: true
description:
tags: react, svg, icons, npm
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u98ksmjxbggchvxmf8ci.png
---

> Cover photo by [Harpal Singh](https://unsplash.com/@aquatium) on [Unsplash](https://unsplash.com/photos/_zKxPsGOGKg)

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](https://www.npmjs.com/).

> Fact: I’ve learnt this from watching [tailwindlabs/heroicons](https://github.com/tailwindlabs/heroicons)

*TLDR: If you want to skip this post and want to check the final work instead, please click here [react-icon-boilerplate](https://github.com/mikunpham/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.

```sh
mkdir react-icons-boilerplate && cd react-icons-boilerplate
yarn init -y
yarn add -D svgo rimraf
```

We will use [svgo](https://github.com/svg/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.

```sh
mkdir raw
```

This is my SVG sample file, I will place it in the raw folder `raw/plus-outline.svg`

```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.

```json
{
  "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:

```js
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](https://jakearchibald.github.io/svgomg/)

This is my optimized [result file](https://github.com/mikunpham/react-icon-boilerplate/blob/main/optimized/plus-outline.svg).

## 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](https://babeljs.io/) and [svgr](https://react-svgr.com/).

```sh
yarn add -D @babel/core @babel/preset-react @svgr/cli @svgr/core camelcase terser
```

You get this build script from here: [script/build.js](https://github.com/mikunpham/react-icon-boilerplate/blob/main/scripts/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

```js
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.

```js
// 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` 

```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

![3539DB0D-179B-46AA-9BB7-2AD78351EB80](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4wt1kq9khqax6r1ttzcz.png) 

And don’t forget to add these lines in your `package.json` before publishing it.

```json
{
  // ...
  "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](https://github.com/features/actions) and [atlassian/changesets](https://github.com/atlassian/changesets) to do this automatically for me.

### changesets

To install and init `changesets`:

```sh
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:

```json
{
  "$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](https://semver.org/) 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.

![86BDEFE4-2AF0-4599-84DF-58E441E81FCD](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n111dxpvih3flzt4khbc.png) 

### 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](https://github.com/mikunpham/react-icon-boilerplate/tree/main/.github/workflows).

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.

![45043714-16A2-4DA1-B4F4-C07276FA1885](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xkwm1r6x78boesn3uuty.png) 

After merging, a release will be created

![22E81BD3-5302-465D-A6E0-BFA0756296EF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ik34ya67jgtymkmyfsw6.png) 

And go check your [npm now](https://www.npmjs.com/package/react-icons-boilerplate).

![8BAF8198-D4BE-4882-B2F7-66EE9973889D](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tmys8auwim6p7tvfos9r.png) 

## What’s next
From now, whenever you have new icons, do these following step.
1. Add them to the `raw` folder.
2. `yarn build`
3. `yarn changeset` , select major/minor/patch bump and write summary.
4. Commit
5. …
6. 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` and `changesets` for making our dev life easier than ever.

![597AFCAB-C5DC-49E1-AF5A-D68232397823](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6h3e1hr5fokzrej0zmn.png)

### Thank you for reading till the end 🎉