Exporting icons from Figma
This example shows how to import icon set from Figma, clean up all icons (including two-tone icons), export icon set as IconifyJSON and SVG.
This specific code is designed to import and clean up Solar icon set.
Process
What is shown in this example?
This code has 3 parts:
- Importing icons from Figma using Figma API.
- Cleaning up icons.
- Exporting icon set as IconifyJSON and individual SVG files.
What is happening in the clean-up process? For each icon it:
- Retrieves icon from IconSet instance as SVG instance.
- Attempts to remove a clip path, if present, which Figma often adds to SVG.
- Parses all colors: replaces known icon colors with black, known two-tone color as gray, keeps white as white. In Figma document icons use many colors, not just black.
- If an icon contains white or two-tone color, applies mask to it.
Code
import-solar.mjs
mjs
import { writeFileSync } from 'node:fs';
import {
cleanupIconKeyword,
convertSVGToMask,
importFromFigma,
isEmptyColor,
parseColorsSync,
removeFigmaClipPathFromSVG,
exportToDirectory,
} from '@iconify/tools';
// Figma file ID. Replace it with your clone of Solar icon set
const file = '';
// Figma API token. Replace it with your API token
const token = '';
// Two-tone color
const twoToneColor = '#808080'; // 50% opacity
// Suffixes for themes
/** @type {Record<string, string>} */
const suffixes = {
'Broken': '-broken',
'Line Duotone': '-line-duotone',
'Linear': '-linear',
'Outline': '-outline',
'Bold': '-bold',
'Bold Duotone': '-bold-duotone',
};
(async () => {
/**
* Import icon set from Figma
*/
const { iconSet } = await importFromFigma({
file,
token,
cacheDir: 'cache',
prefix: 'solar',
depth: 3,
pages: ['🔥 Icon Library'],
iconNameForNode: (node) => {
if (node.type !== 'COMPONENT') {
return null;
}
const parts = node.name.split('/');
if (parts.length < 3) {
return null;
}
const theme = parts.shift().trim();
if (!suffixes[theme]) {
throw new Error(`Unknown theme in name: "${node.name}"`);
}
const category = parts.shift().trim();
const name = parts.shift().trim();
if (parts.length) {
throw new Error(`Too many elements in name: "${node.name}"`);
}
const keyword = cleanupIconKeyword(name) + suffixes[theme];
return keyword;
},
afterImportingIcon: (node, iconSet) => {
// Add category
const parts = node.name.split('/');
if (parts.length < 3) {
return;
}
const theme = parts.shift().trim();
if (!suffixes[theme]) {
throw new Error(`Unknown theme in name: "${node.name}"`);
}
const category = parts.shift().trim();
const name = parts.shift().trim();
if (parts.length) {
throw new Error(`Too many elements in name: "${node.name}"`);
}
const keyword = cleanupIconKeyword(name) + suffixes[theme];
iconSet.toggleCategory(keyword, category, true);
},
});
/**
* Parse all icons
*/
iconSet.forEachSync((name, type) => {
if (type !== 'icon') {
return;
}
const svg = iconSet.toSVG(name);
if (!svg) {
return;
}
const backup = svg.toString();
// Remove clip path
removeFigmaClipPathFromSVG(svg);
// Check colors
let hasWhite = false;
let hasDuotone = false;
parseColorsSync(svg, {
callback: (attr, colorString, color) => {
if (color && isEmptyColor(color)) {
return color;
}
switch (colorString.toLowerCase()) {
case '#000':
case 'black':
case '#1c274c':
case '#1c274d':
return '#000';
case '#8e93a6':
hasDuotone = true;
return twoToneColor;
case '#fff':
case 'white':
hasWhite = true;
return '#fff';
}
// Unknown color
console.log(backup);
throw new Error(`Bad color in ${name}: ${colorString}`);
},
});
// Mask icon
if (hasWhite || hasDuotone) {
if (
!convertSVGToMask(svg, {
color: '#000',
custom: (color) => {
switch (color) {
case twoToneColor:
return color;
}
},
})
) {
console.log(backup);
throw new Error(`Failed to convert "${name}" to mask`);
}
}
if (svg.toString() !== backup) {
iconSet.fromSVG(name, svg);
}
});
/**
* Export icon set
*/
// Export icon set as IconifyJSON
writeFileSync(
'solar.json',
JSON.stringify(iconSet.export(), null, '\t'),
'utf8'
);
// Export icons as SVG
await exportToDirectory(iconSet, {
target: 'svg',
});
})();