Skip to content

将 SVG 集合转换为 Iconify JSON

本示例展示了如何将包含大量 SVG 文件的目录转换为 Iconify JSON 格式。

作为数据源,本示例使用了来自 Templarian/MaterialDesign-SVG 仓库的 Material Design 图标,该仓库也可作为 @mdi/svg NPM 包获取。

安装依赖:

npm install @iconify/tools @mdi/svg --save

然后创建文件 convert-mdi.ts 并填入以下内容:

convert-mdi.ts
tsimport { promises as fs } from 'fs';
import { dirname } from 'path';

// Installation: npm install --save-dev @iconify/tools @mdi/svg
import {
   importDirectory,
   cleanupSVG,
   parseColors,
   isEmptyColor,
   runSVGO,
   cleanupIconKeyword,
} from '@iconify/tools';
import type { IconifyInfo } from '@iconify/types';

// File to save icon set to
const target = 'json/mdi.json';

// SVG files location
const sourcePackageJSON = require.resolve('@mdi/svg/package.json');
const sourceSVGDir = dirname(sourcePackageJSON) + '/svg';

// Metadata (specific to MDI example, remove this for your code)
const metadataSource: string | null = require.resolve('@mdi/svg/meta.json');

// Prefix to use for icon set
const prefix = 'mdi';

// Expected icon size. Used in validating icons, remove if you do not need to validate icons
const expectedSize = 24;

// Icon set information
const info: IconifyInfo = {
   name: 'Material Design Icons',
   author: {
       name: 'Austin Andrews',
       url: 'https://github.com/Templarian/MaterialDesign',
   },
   license: {
       title: 'Open Font License',
       url: 'https://raw.githubusercontent.com/Templarian/MaterialDesign/master/LICENSE',
       spdx: 'OFL-1.1',
   },
   height: 24,
   samples: ['account-check', 'bell-alert-outline', 'calendar-edit'],
};

// Import icons
(async function () {
   // Import icons
   const iconSet = await importDirectory(sourceSVGDir, {
       prefix,
   });

   // Set info
   iconSet.info = info;

   // Validate, clean up, fix palette and optimise
   await iconSet.forEach((name, type) => {
       if (type !== 'icon') {
           return;
       }

       // Get SVG instance for parsing
       const svg = iconSet.toSVG(name);
       if (!svg) {
           // Invalid icon
           iconSet.remove(name);
           return;
       }

       // Check icon size
       const viewBox = svg.viewBox;
       if (viewBox.width !== expectedSize || viewBox.height !== expectedSize) {
           console.error(
               `Icon ${name} has invalid dimensions: ${viewBox.width} x ${viewBox.height}`
           );
           iconSet.remove(name);
           return;
       }

       // Clean up and optimise icons
       try {
           // Clean up icon code
           cleanupSVG(svg);

           // Replace color with currentColor, add if missing
           parseColors(svg, {
               defaultColor: 'currentColor',
               callback: (attr, colorStr, color) => {
                   return !color || isEmptyColor(color) ? colorStr : 'currentColor';
               },
           });

           // Optimise
           runSVGO(svg);
       } catch (err) {
           // Invalid icon
           console.error(`Error parsing ${name}:`, err);
           iconSet.remove(name);
           return;
       }

       // Update icon from SVG instance
       iconSet.fromSVG(name, svg);
   });
   console.log(`Imported ${iconSet.count()} icons`);

   // Add metadata from meta.json
   if (metadataSource) {
       interface MDIMetaDataItem {
           id: string;
           name: string;
           codepoint: string;
           aliases: string[];
           tags: string[];
           author: string;
           version: string;
       }

       const metaContent = JSON.parse(
           await fs.readFile(metadataSource, 'utf8')
       ) as MDIMetaDataItem[];
       metaContent.forEach((entry) => {
           const { name, aliases, tags } = entry;
           const cleanName = cleanupIconKeyword(name);
           if (iconSet.entries[cleanName] === void 0) {
               console.error(`Missing icon: ${cleanName}`);
               return;
           }

           // Add categories
           tags.forEach((category) => {
               iconSet.toggleCategory(cleanName, category, true);
           });

           // Add aliases
           aliases.forEach((alias) => {
               const cleanAlias = cleanupIconKeyword(alias);
               if (iconSet.entries[cleanAlias] === void 0) {
                   iconSet.setAlias(cleanAlias, cleanName);
               }
           });
       });
   }

   // Export to IconifyJSON, convert to string
   const output = JSON.stringify(iconSet.export(), null, '\t');

   // Create directory for output if missing
   const dir = dirname(target);
   try {
       await fs.mkdir(dir, {
           recursive: true,
       });
   } catch (err) {
       //
   }

   // Save to file
   await fs.writeFile(target, output, 'utf8');

   console.log(`Saved ${target} (${output.length} bytes)`);
})().catch((err) => {
   console.error(err);
});

假设 TypeScript 已配置为编译到 lib 目录,将文件编译为 JavaScript 并运行:

node lib/convert-mdi

如果您不使用 TypeScript,请从代码中移除类型注解。这应该不难,因为需要删除的行数并不多。

准备好的项目可在 Iconify Tools GitHub 仓库 中获取。

它是如何工作的?

上述代码中的注释解释了具体过程。

流程很简单:

  1. importDirectory()@mdi/svg 包的 "svg" 目录导入所有图标。
  2. 使用 iconSet.forEach() 遍历所有图标以执行以下操作:
    • 使用 toSVG() 获取可通过各种函数进行操作的 SVG 实例。
    • 使用 cleanupSVG() 清理代码(MDI 的代码已经很干净,因此无需清理,但对于其他图标集则是必要的)。
    • 使用 parseColors() 将默认颜色更改为 "currentColor"。
    • 使用 runSVGO() 优化图标代码。
    • 使用 iconSet.fromSVG() 更新图标集中的图标数据。
  3. 然后脚本处理元数据:为所有图标添加分类和别名。
  4. 使用 iconSet.export() 将图标集导出为 JSON 文件。