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
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
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"]
}
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"
}
Run the command to transform all SVGs in the icons/
folder into React components inside src/icons/
:
npm run generate-icons
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();
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.`);
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'],
});
Add the following script to your package.json
:
"scripts": {
"build": "tsup"
}
Run the build command:
npm run build
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"
}
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
3. Publish your package
Once logged in, you can publish your library to npm:
npm publish --access public
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:
- How to Create a React Component Library Using Vite’s Library Mode – A great guide on building a tree shakeable React component library with Vite’s library mode.
- How to Write a Tree-Shakable Component Library.
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)
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:
let me know if you have any other questions!