Skip to content

从 Figma 导出图标

本示例展示了如何从 Figma 导入图标集,清理所有图标(包括双色图标),并将图标集导出为 IconifyJSON 和 SVG 格式。

此特定代码专为导入和清理 Solar 图标集而设计。

流程

本示例展示了什么?

此代码包含 3 个部分:

  • 使用 Figma API 从 Figma 导入图标。
  • 清理图标。
  • 将图标集导出为 IconifyJSON 和独立的 SVG 文件。

清理过程中会执行什么操作?对于每个图标,它会:

  • IconSet 实例中检索图标,并将其作为 SVG 实例。
  • 尝试移除裁剪路径(如果存在),Figma 通常会将其添加到 SVG 中。
  • 解析所有颜色:将已知的图标颜色替换为黑色,已知的双色颜色替换为灰色,保留白色不变。在 Figma 文档中,图标使用了多种颜色,而不仅仅是黑色。
  • 如果图标包含白色或双色,则为其应用遮罩。

代码

import-solar.mjs
mjsimport { 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',
   });
})();