156 lines
4.7 KiB
JavaScript
156 lines
4.7 KiB
JavaScript
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();
|