import path from 'path'; import { transform } from '@svgr/core'; import axios from 'axios'; import chalk from 'chalk'; import dotenv from 'dotenv'; import fs from 'fs-extra'; import _ from 'lodash'; import * as Figma from 'figma-js'; dotenv.config(); const componentsDir = 'src/components'; // Add .env file const FIGMA_API_TOKEN = process.env.FIGMA_API_TOKEN; const FIGMA_FILE_ID = process.env.FIGMA_FILE_ID; const FIGMA_CANVAS = process.env.FIGMA_CANVAS; if (!FIGMA_API_TOKEN) { throw new Error('Missing environment variable FIGMA_API_TOKEN'); } if (!FIGMA_FILE_ID) { throw new Error('Missing environment variable FIGMA_FILE_ID'); } if (!FIGMA_CANVAS) { throw new Error('Missing environment variable FIGMA_CANVAS'); } const figmaApi = Figma.Client({ personalAccessToken: FIGMA_API_TOKEN }); const getSvgs = async ({ fileId, canvas, group }) => { const file = await figmaApi.file(fileId); const { document } = file.data; const iconsNode = document.children.find(({ name }) => name === canvas); if (!iconsNode) { throw new Error(`Couldn't find page with name ${canvas}`); } const usingIconNodes = iconsNode.children.find(({ name }) => name === group)?.children || []; const usingNodeId = usingIconNodes.map(({ id }) => id); const svgs = await figmaApi.fileImages(fileId, { format: 'svg', ids: usingNodeId, }); return usingIconNodes.map(({ id, name }) => ({ id, name, url: svgs.data.images[id] })); }; const downloadSVGsData = async (data, batchSize = 20, delayBetweenBatches = 500) => { const results = []; const batchCount = Math.ceil(data.length / batchSize); for (let i = 0; i < batchCount; i++) { const batchData = data.slice(i * batchSize, (i + 1) * batchSize); console.log(`Processing batch ${i + 1}/${batchCount}, containing ${batchData.length} requests`); const batchResults = await Promise.all( batchData.map(async (dataItem) => { try { const downloadedSvg = await axios.get(dataItem.url); return { ...dataItem, data: downloadedSvg.data, success: true, }; } catch (error) { console.error(`Failed to download ${dataItem.url}:`, error.message); return { ...dataItem, success: false, }; } }) ); results.push(...batchResults); if (i < batchCount - 1) { await new Promise((resolve) => setTimeout(resolve, delayBetweenBatches)); } } return results; }; const transformReactComponent = (svgList) => { if (!fs.existsSync(componentsDir)) { fs.mkdirSync(componentsDir); } svgList.forEach((svg) => { if (!svg.success) return; const svgCode = svg.data; const svgName = svg.name.split('/').pop(); const camelCaseInput = _.camelCase(svgName); const componentName = camelCaseInput.charAt(0).toUpperCase() + camelCaseInput.slice(1); const componentFileName = `${componentName}.tsx`; // Converts SVG code into React code using SVGR library const componentCode = transform.sync( svgCode, { typescript: true, icon: true, replaceAttrValues: { '#000': 'currentColor', }, plugins: [ // Clean SVG files using SVGO '@svgr/plugin-svgo', // Generate JSX '@svgr/plugin-jsx', // Format the result using Prettier '@svgr/plugin-prettier', ], }, { componentName } ); // 6. Write generated component to file system fs.outputFileSync(path.resolve(componentsDir, componentFileName), componentCode); // fs.outputFileSync(path.resolve('src/icons', `${svgName}.svg`), svg.data); }); }; const genIndexContent = () => { let indexContent = ''; const indexPath = path.resolve('src/index.ts'); fs.readdirSync(componentsDir).forEach((componentFileName) => { // Convert name to pascal case const componentName = componentFileName.split('.')[0]; // Export statement const componentExport = `export { default as ${componentName} } from './components/${componentName}';\n`; indexContent += componentExport; }); // Write the content to file system fs.writeFileSync(indexPath, indexContent); }; const generate = async () => { console.log(chalk.magentaBright('-> Fetching icons metadata')); const svgs = await getSvgs({ fileId: FIGMA_FILE_ID, canvas: FIGMA_CANVAS, group: 'using' }); console.log(chalk.blueBright('-> Downloading SVG code')); const svgsData = await downloadSVGsData(svgs); console.log(chalk.cyanBright('-> Converting to React components')); transformReactComponent(svgsData); console.log(chalk.yellowBright('-> Writing exports components')); genIndexContent(); console.log(chalk.greenBright('-> All done! ✅')); }; generate();