iconify vite插件打包本地svg图标实现动态渲染

iconify 官网

入口

效果

在这里插入图片描述

在这里插入图片描述

使用方式

iconify有多种使用方式,结合iconify提供的 Icon 组件可完成动态渲染功能。

  <!-- 使用方式 {前缀}-{图标集合}-{图标名} -->
  <i-ep-edit />
  <i-mdi-account-box style="font-size: 2em; color: red"/>
  <!-- 结合element plus -->
  <el-icon><i-ep-menu /></el-icon>
  <Icon icon="ep:menu" />

渲染动态图标

 <!-- custom:${item.icon} => custom 是本地图标的自定义前缀,在vite.config中配置 -->
   // import {Icon} from 'iconify/vue'
    <el-icon>
    	 <Icon :icon="`custom:${item.icon}`" width="28" height="28" />
    </el-icon>       

安装依赖

pnpm i @iconify/vue

pnpm i @iconify/utils

pnpm i @iconify/tools

插件代码 vite-iconify-bundle.js

import { promises as fs } from "node:fs";
import {
  importDirectory,
  cleanupSVG,
  parseColors,
  isEmptyColor,
  runSVGO,
} from "@iconify/tools";
import { getIcons, stringToIcon, minifyIconSet } from "@iconify/utils";

const component = "@iconify/vue";

// Set to true to use require() instead of import
const commonJS = false;

let iconCount = 0;

export default function (sources) {
  const virtualModuleId = "virtual:customIcon";
  const resolvedVirtualModuleId = "\0" + virtualModuleId;

  return {
    name: "vite-iconify-bundle", // 必须的,将会在 warning 和 error 中显示
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId;
      }
    },
    async load(id) {
      if (id === resolvedVirtualModuleId) {
        const iconSet = await createBundleTask(sources);

        return iconSet;
      }
    },
  };
}

async function createBundleTask(sources) {
  let bundle = commonJS
    ? "const { addCollection } = require('" + component + "');\n\n"
    : "import { addCollection } from '" + component + "';\n\n";

  // 将sources.icons转换为sources.json
  if (sources.icons) {
    const sourcesJSON = sources.json ? sources.json : (sources.json = []);

    // Sort icons by prefix
    const orgainzedList = organizeIconsList(sources.icons);
    for (const prefix in orgainzedList) {
      const filename = require.resolve(`@iconify/json/json/${prefix}.json`);
      sourcesJSON.push({
        filename,
        icons: orgainzedList[prefix],
      });
    }
  }

  // 打包 JSON 文件
  if (sources.json) {
    for (let i = 0; i < sources.json.length; i++) {
      const item = sources.json[i];

      // 加载 icon集合
      const filename = typeof item === "string" ? item : item.filename;
      let content = JSON.parse(await fs.readFile(filename, "utf-8"));

      // Filter icons
      if (typeof item !== "string" && item.icons?.length) {
        const filteredContent = getIcons(content, item.icons);
        if (!filteredContent) {
          throw new Error(`Cannot find required icons in ${filename}`);
        }
        content = filteredContent;
      }

      // 移除元数据并且添加到bundle
      removeMetaData(content);
      minifyIconSet(content);

      bundle += "addCollection(" + JSON.stringify(content) + ");\n";

      console.log(`Bundled icons from ${filename}`);
    }
  }

  // 自定义 SVG 文件
  if (sources.svg) {
    for (let i = 0; i < sources.svg.length; i++) {
      const source = sources.svg[i];
      // 导入图标
      const iconSet = await importDirectory(source.dir, {
        prefix: source.prefix,
      });

      // 验证,清理,修复颜色并优化
      await iconSet.forEach(async (name, type) => {
        if (type !== "icon") {
          return;
        }
        // 获取SVG实例以进行分析
        const svg = iconSet.toSVG(name);
        if (!svg) {
          // 无效的图标
          iconSet.remove(name);
          return;
        }

        // 清除并且优化图标
        try {
          // Clean up icon code

          await cleanupSVG(svg);

          if (source.monotone) {
            // Replace color with currentColor, add if missing
            // If icon is not monotone, remove this code
            await parseColors(svg, {
              defaultColor: "currentColor",
              callback: (attr, colorStr, color) => {
                return !color || isEmptyColor(color)
                  ? colorStr
                  : "currentColor";
              },
            });
          }

          // Optimise
          await runSVGO(svg);
        } catch (err) {
          // Invalid icon
          console.error(`Error parsing ${name} from ${source.dir}:`, err);
          iconSet.remove(name);
          return;
        }
        iconCount = iconSet.count();
        // Update icon from SVG instance
        iconSet.fromSVG(name, svg);
      });
      // Export to JSON
      const content = iconSet.export();
      bundle += "addCollection(" + JSON.stringify(content) + ");\n";
    }
  }

  console.log(
    `\n图标打包完成! 总共打包了${iconCount}个图标,大小:(${bundle.length} bytes)\n`
  );
  // Save to file
  return bundle;
}

/**
 * Sort icon names by prefix
 * @param icons
 * @returns icons
 */
function organizeIconsList(icons) {
  const sorted = Object.create(null);
  icons.forEach((icon) => {
    const item = stringToIcon(icon);
    if (!item) {
      return;
    }

    const prefix = item.prefix;
    const prefixList = sorted[prefix] ? sorted[prefix] : (sorted[prefix] = []);

    const name = item.name;
    if (prefixList.indexOf(name) === -1) {
      prefixList.push(name);
    }
  });

  return sorted;
}

// 从icon集合移除元数据
function removeMetaData(iconSet) {
  const props = [
    "info",
    "chars",
    "categories",
    "themes",
    "prefixes",
    "suffixes",
  ];
  props.forEach((prop) => {
    delete iconSet[prop];
  });
}

vite.config.js配置

import ViteIconifyBundle from './plugins/vite-iconify-bundle'
import path from 'node:path'

export default defineConfig({
	plugins: [
		ViteIconifyBundle({
      // 打包自定义svg图标
      svg: [
        {
          dir: path.resolve(__dirname, './src/assets/svg'),
          monotone: false,
          prefix: 'custom', // 自定义本地图标前缀
        },
      ],

      // 从 iconify 打包 icon 集合 需要先安装依赖
       icons: [
         "mdi:home",
         "mdi:account",
         "mdi:login",
         "mdi:logout",
         "octicon:book-24",
         "octicon:code-square-24"
       ]

      // 自定义 JSON 文件
       json: [
         // Custom JSON file
         // 'json/gg.json',
         // Iconify JSON file (@iconify/json is a package name, /json/ is directory where files are, then filename)
         require.resolve('@iconify/json/json/tabler.json'),
         // Custom file with only few icons
         {
           filename: require.resolve('@iconify/json/json/line-md.json'),
           icons: ['home-twotone-alt', 'github', 'document-list', 'document-code', 'image-twotone'],
         },
       ],
    }),
	]
})

在main.js中导入

import "virtual:customIcon";
posted @ 2025-03-26 14:21  青花·  阅读(17)  评论(0)    收藏  举报  来源