当前位置:   article > 正文

基于iconify开发vite插件,打包本地文件夹中的svg图标离线使用,iconify动态渲染图标_iconify离线

iconify离线

背景

在项目中使用svg图标是一个非常常见的需求,目前常用的方案有iconfont矢量图标库提供的方案,基于svg-sprite-loader插件处理的方案,但是这些方案会生成大量的dom节点,性能底。因此iconify会是更好的选择,想要基于iconify使用v-for的方式动态渲染图标必须要本地打包图标。

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" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

渲染动态图标

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

使用iconify打包离线图标库和本地的svg图标

效果在这里插入图片描述

在main.js中导入

import "virtual:customIcon";
  • 1

在这里插入图片描述

vite.config.ts配置

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'],
         },
       ],
    }),
	]
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

完整代码

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

interface BundleScriptCustomSVGConfig {
  // 存放SVG文件的目录
  dir: string;

  // 是否为多色图标
  monotone: boolean;

  // Icon 集合的前缀
  prefix: string;
}

interface BundleScriptCustomJSONConfig {
  // JSON文件的路径
  filename: string;

  // 要导入的图标名称数组,如果不写则全部导入
  icons?: string[];
}

interface BundleScriptConfig {
  // 自定义导入的svg图标集合
  svg?: BundleScriptCustomSVGConfig[];

  // 要从@iconify/json打包的图标集
  icons?: string[];

  // 要打包的JSON文件列表
  json?: (string | BundleScriptCustomJSONConfig)[];
}

const component = "@iconify/vue";

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

let iconCount: number = 0;

export default function (sources: BundleScriptConfig): PluginOption {
  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: BundleScriptConfig) {
  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")) as IconifyJSON;

      // 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: string[]) {
  const sorted: Record<string, string[]> = 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: IconifyJSON) {
  const props: (keyof IconifyMetaData)[] = ["info", "chars", "categories", "themes", "prefixes", "suffixes"];
  props.forEach(prop => {
    delete iconSet[prop];
  });
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/803659
推荐阅读
相关标签
  

闽ICP备14008679号