DEV Community

Cover image for How to Build an Icon Library with TypeScript, Tsup, and SVGR
Gustavo Henrique
Gustavo Henrique

Posted on

How to Build an Icon Library with TypeScript, Tsup, and SVGR

Want to create an icon library for your React project with TypeScript? With Tsup and SVGR, you can efficiently turn SVG files into reusable React components. Let’s go straight to the point and build this from scratch.


Project Folder Structure

Before diving in, here’s the folder structure we’re aiming for:

my-icons-library/
├── icons/                  # SVG files go here
├── src/
│   └── icons/              # Generated React components
├── scripts/                # Utility scripts for custom tasks
├── index.ts                # Entry point of the library
├── tsconfig.json           # TypeScript config file
├── package.json            # Project settings
└── tsup.config.ts          # Tsup config file for building the project
Enter fullscreen mode Exit fullscreen mode

Step 1: Setting Up the Project

First, create a new folder, initialize the project, and install dependencies:

mkdir my-icons-library
cd my-icons-library
npm init -y
npm install react react-dom typescript tsup @svgr/cli @types/react @types/react-dom svgo --save-dev
Enter fullscreen mode Exit fullscreen mode

This sets up your React environment with TypeScript, SVGR, and Tsup for bundling.


Step 2: Configuring TypeScript

Let’s configure TypeScript. Create a tsconfig.json file and add the following content:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "jsx": "react-jsx",
    "declaration": true,
    "declarationDir": "dist/types",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "isolatedModules": true
  },
  "include": ["src/**/*", "scripts/*", "index.ts"],
  "exclude": ["node_modules", "dist"]
}
Enter fullscreen mode Exit fullscreen mode

This config ensures TypeScript compiles the icons into proper .d.ts type definitions and outputs the compiled code in the dist folder.


Step 3: Generating Icons with SVGR

Now, let’s turn your SVG files into React components with SVGR. Add the following script to your package.json:

"scripts": {
  "generate-icons": "npx @svgr/cli --typescript --icon --out-dir src/icons icons"
}
Enter fullscreen mode Exit fullscreen mode

Run the command to transform all SVGs in the icons/ folder into React components inside src/icons/:

npm run generate-icons
Enter fullscreen mode Exit fullscreen mode

Step 4: Customizing Icons (Size & Color)

You’ll likely want to modify your icons to allow dynamic sizing and coloring. Create a script called modify-icons.ts in the scripts/ folder:

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const ICONS_DIR = path.join(__dirname, '..', 'src', 'icons');

const modifyIcon = (filePath: string) => {
  let content = fs.readFileSync(filePath, 'utf-8');

  const propsRegex = /SVGProps<SVGSVGElement>/;
  if (propsRegex.test(content)) {
    content = content.replace(
      propsRegex,
      `(SVGProps<SVGSVGElement> & { size?: string | number; color?: string })`
    );
  }

  const svgTagRegex = /<svg([^>]*)>/;
  const svgMatch = content.match(svgTagRegex);
  if (svgMatch) {
    const svgAttributes = svgMatch[1];

    let newSvgAttributes = svgAttributes
      .replace(/width="[^"]*"/, '{...(props.size ? { width: props.size, height: props.size } : { width: "1em", height: "1em" })}')
      .replace(/height="[^"]*"/, '');

    newSvgAttributes += ' fill={props.color || "currentColor"}';

    content = content.replace(svgTagRegex, `<svg${newSvgAttributes}>`);
  }

  fs.writeFileSync(filePath, content, 'utf-8');
};

const main = () => {
  fs.readdir(ICONS_DIR, (err, files) => {
    if (err) {
      console.error('Error reading icons directory:', err);
      process.exit(1);
    }

    files.forEach((file) => {
      const filePath = path.join(ICONS_DIR, file);
      if (path.extname(file) === '.tsx') {
        try {
          modifyIcon(filePath);
          console.log(`Modified: ${file}`);
        } catch (error) {
          console.error(`Error modifying ${file}:`, error);
        }
      }
    });
  });
};

main();
Enter fullscreen mode Exit fullscreen mode

Run this script after generating the icons to add dynamic size and color properties to your components.


Step 5: Automatically Exporting Icons

To avoid manually exporting each icon, create a script called generate-index.ts in the scripts/ folder that will auto-generate an index.ts file for all your icons:

import { readdirSync, writeFileSync } from 'fs';
import { resolve, join, basename } from 'path';

const iconsDir = resolve('src/icons');
const indexPath = join(resolve(), 'index.ts');

const files = readdirSync(iconsDir).filter((file) => file.endsWith('.tsx'));

const exportStatements = files
  .map((file) => {
    const componentName = basename(file, '.tsx');
    return `export { default as ${componentName} } from './src/icons/${componentName}';`;
  })
  .join('\n');

writeFileSync(indexPath, exportStatements);

console.log(`Generated index.ts with ${files.length} icon exports.`);
Enter fullscreen mode Exit fullscreen mode

Run this script to generate the index.ts file with exports for all your icons.


Step 6: Bundling with Tsup

Now it’s time to bundle the project using Tsup. Create a tsup.config.ts file:

import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['index.ts'],
  outDir: 'dist',
  format: ['cjs', 'esm'],
  dts: true,
  splitting: true,
  sourcemap: true,
  clean: true,
  external: ['react', 'react-dom'],
});
Enter fullscreen mode Exit fullscreen mode

Add the following script to your package.json:

"scripts": {
  "build": "tsup"
}
Enter fullscreen mode Exit fullscreen mode

Run the build command:

npm run build
Enter fullscreen mode Exit fullscreen mode

Your icon library is now bundled and ready to be used!


Step 7: Configuring the package.json

Here’s how your package.json should look like for publishing your library to npm:

{
  "name": "my-icons-library",
  "version": "1.0.0",
  "description": "A TypeScript React icon library",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist",
    "src"
  ],
  "scripts": {
    "start": "vite",
    "generate-icons": "npx @svgr/cli --typescript --icon --out-dir src/icons icons",
    "generate-and-modify-icons": "npm run generate-icons && node scripts/modify-icons.ts",
    "build": "tsup",
    "prepublishOnly": "npm run build"
  },
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "tsup": "^5.0.0",
    "@svgr/cli": "^5.0.0",
    "@types/react": "^18.0.0",
    "@types/react-dom": "^18.0.0"
  },
  "author": "Your Name",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode

This package.json setup ensures that your library is packaged correctly and ready for npm publishing.


Step 8: Publishing to npm

Here’s how you can publish your icon library to npm:

1. Create an npm account

If you don’t already have one, create an account on npm.

2. Log in via the terminal

In your project’s root directory, log in to npm:

npm login
Enter fullscreen mode Exit fullscreen mode

3. Publish your package

Once logged in, you can publish your library to npm:

npm publish --access public
Enter fullscreen mode Exit fullscreen mode

This command will upload your package to the npm registry, and it will be available for others to install and use.


Conclusion

By following these steps, you’ve created a fully functional icon library with TypeScript, SVGR, and Tsup. Your icons are now reusable, customizable React components, and you’ve learned how to publish them to npm for the world to use.


I hope you found this guide helpful! If you'd like to see the full repository, feel free to check it out at https://github.com/avoguga/icon-library-template.

For those looking to deepen their understanding of how to create and publish a TypeScript library, I highly recommend checking out the following articles:

Additionally, if you're interested in learning more about how to bundle a tree-shakable TypeScript library using Tsup, I recommend this detailed guide:

These resources will offer you a broader perspective on the best practices and techniques for building and distributing libraries efficiently.

Top comments (1)

Collapse
 
avoguga profile image
Gustavo Henrique • Edited

i just forgot to include how you can actually use the icons in a react application.

import the icon you want to use, and that's it:

import { Star } from './icons';
// you can use it inside your component like this:

const IconComponent= () => {
  return <Star size={1000} color="red" />;
};
Enter fullscreen mode Exit fullscreen mode

let me know if you have any other questions!