赞
踩
在项目中使用svg图标是一个非常常见的需求,目前常用的方案有iconfont矢量图标库提供的方案,基于svg-sprite-loader
插件处理的方案,但是这些方案会生成大量的dom节点,性能底。因此iconify会是更好的选择,想要基于iconify使用v-for的方式动态渲染图标必须要本地打包图标。
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>
import "virtual:customIcon";
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'], }, ], }), ] })
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]; }); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。