iconify vite插件打包本地svg图标实现动态渲染
基于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";

浙公网安备 33010602011771号