Build an SVG icon library in React

Working with design assets like svgs doesn't need to be hard. SVGR can help us transform svgs into ready to use React components to easily integrate with our projects.

SVGR transforms SVGs into React Components

Let's expolore SVGR by creating an SVG icon library and bundle it using Rollup to get it ready to be published on NPM.

View final project on NPM


Initialize the project

Make a project directory and cd into it.

mkdir tyger-avatar && cd tyger-avatar

Initialize NPM in the root folder of our project.

npm init -y

Create an svg assets folder and add your assets. The name of the svg will be later used to generate the React Component name.

├── assets │ ├── sampleOne.svg │ └── sampleTwo.svg └── package.json

SVGR Installation

Let's transform our svg files into reusable React Components using SVGR. Our svgs will be wrapped into a React component with customized props.

First, install SVGR CLI as a dev dependency.

npm install @svgr/cli --save-dev

Next, we need to add the SVGR script to package.json. There are several customization options that can help us optimize our generated React component, including some advanced options, like creating a custom template.

For this project, we are using the following options:

  • --icon: ViewBox is preserved and SVG inherits text size.
  • --title-prop: Add title tag via title property.
  • --typescript: Generates .tsx files.
  • -d Defines the directories. Output src/TrAvatars and input assets.
"scripts": { "svgr": "svgr --icon --title-prop --no-dimensions --typescript -d src/TrAvatars assets", },

Checkout more customization options.


SVGR Template (optional)

The SVGR template allow us to optionally customize the final generated component. The custom template exports a function called by the babel plugin (internally by SVGR). The template must return a Babel AST. This template is for advanced customizations. In most cases we won't need it.

Let's create a custom template file to customize the name of our exported component. Essentially, we're using the default template and removing the prefix "Svg" name from the generated component. view svgr template.

// svgr-template.js function defaultTemplate( { template }, opts, { imports, interfaces, componentName, props, jsx } ) { const tygerIcon = `${componentName.name.replace('Svg', '')}`; const plugins = ['jsx']; if (opts.typescript) { plugins.push('typescript'); } const typeScriptTpl = template.smart({ plugins }); return typeScriptTpl.ast`${imports} ${interfaces} function ${tygerIcon}(${props}) { return ${jsx}; } export default ${tygerIcon} `; } module.exports = defaultTemplate;

We need to let SVGR know about our file template. In the root of our directory create an .svgrrc.js file.

// .svgrrc.js module.exports = { template: require('./svgr-template'), };

SVGR Transformation

We can convert our svgs to react components by running:

npm run svgr

This script will generate our React components and an index.tsx entry point with all of the individual components. We will be using this file when we bundle our icon library.

├── assets │ ├── SampleOne.svg │ └── SampleTwo.svg ├── src | |-- TrAvatars | | |-- SvgSampleOne.tsx | | |-- SvgSampleTwo.tsx | | └── index.tsx └── package.json

Install React and TS

Now, install React and TypeScript as dev dependency.

npm i --save-dev react @types/react typescript

In package.json add this snippet to use React as a peer dependency.

"peerDependencies": { "react": "^16.8.0" }

Lastly, let's create a tsconfig.json in the root directory to specify the root files and the compiler options for the project.

{ "compilerOptions": { "target": "es5", "outDir": "dist", "lib": ["es6", "dom", "es2016", "es2017"], "declaration": true, "declarationDir": "dist", "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react" }, "include": ["src"], "exclude": ["node_modules", "dist"] }

Bundle with Rollup

Rollup is a module bundler for JavaScript. It's easy to configure and the perfect fit for our icon library. Install Rollup and the plugins that we need.

npm i --save-dev rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve rollup-plugin-peer-deps-external

Rollup config

// rollup.config.js import peerDepsExternal from 'rollup-plugin-peer-deps-external'; import resolve from '@rollup/plugin-node-resolve'; import typescript from 'rollup-plugin-typescript2'; const packageJson = require('./package.json'); export default { // entry points input: './src/index.tsx', // output files output: [ { file: packageJson.module, format: 'esm', // ES Modules sourcemap: true, }, ], // Plugins array plugins: [ peerDepsExternal(), // prevents bundling peerDependencies resolve(), // resolves package entrypoints typescript({ useTsconfigDeclarationDir: true }), // typescript ], };

Now, add the entries to our project, in package.json.

/* package.json */ "main": "dist/index.d.js", "module": "dist/index.esm.js", "types": "dist/index.d.ts",

Last, let's streamline the build process in one step, so that when we run npm run build.

  • Our rc/TrAvatars and dist will have the most updated assets (delete/generate).
  • The svgr script will run and create our React components with our options.
  • And finally the rollup bundler will compile our library.

In package.json update our scripts.

/* package.json */ "scripts": { "clean": "rm -rf src/TrAvatars && rm -rf dist", "svgr": "svgr --icon --title-prop --no-dimensions --typescript -d src/TrAvatars assets", "build": "npm run clean && npm run svgr && rollup -c", },

Consume our package

Now, we can publish our React SVG Icon Library on NPM and consume it in multiple projects. Also, we can add Storybook to have a visual reference to our library.