从 Figma 导出图标
本示例展示了如何从 Figma 导入图标集,清理所有图标(包括双色图标),并将图标集导出为 IconifyJSON 和 SVG 格式。
此特定代码专为导入和清理 Solar 图标集而设计。
流程
本示例展示了什么?
此代码包含 3 个部分:
- 使用 Figma API 从 Figma 导入图标。
- 清理图标。
- 将图标集导出为 IconifyJSON 和独立的 SVG 文件。
清理过程中会执行什么操作?对于每个图标,它会:
- 从 IconSet 实例中检索图标,并将其作为 SVG 实例。
- 尝试移除裁剪路径(如果存在),Figma 通常会将其添加到 SVG 中。
- 解析所有颜色:将已知的图标颜色替换为黑色,已知的双色颜色替换为灰色,保留白色不变。在 Figma 文档中,图标使用了多种颜色,而不仅仅是黑色。
- 如果图标包含白色或双色,则为其应用遮罩。
代码
import-solar.mjs
mjs
import { writeFileSync } from 'node:fs';
import {
cleanupIconKeyword,
convertSVGToMask,
importFromFigma,
isEmptyColor,
parseColors,
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;
parseColors(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',
});
})();