更新记录

1.0.0(2026-01-26)

  • feat: 纯离线、自包含的 Iconify SVG 渲染(uni-app x:Android/iOS/Harmony)
  • feat: <HansIconify /> 支持 icon/size/width/height/color/fallbackIcon + load/error
  • feat: <HansIconifyData /> 支持直接传 IconData 渲染
  • feat: 提供离线注册 API:registerIcon/registerCollection/getIconData
  • fix(android): 按屏幕 density 渲染位图,避免高分屏缩放导致的模糊/视觉差异
  • fix: SVG 显式添加 preserveAspectRatio,跨平台表现更一致
  • docs: README 补充静态图标生成脚本与用法说明

平台兼容性

uni-app x(4.76)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - -

hans-iconify(uni-app x,离线 Iconify)

一个 纯离线自包含渲染器 的 Iconify SVG 图标组件,支持两种用法:

  • 静态图标组件(推荐,需生成):按需引入 icons/<prefix>/*.uvue(SVG 数据内置在组件里)
  • 运行时注册:通过 registerIcon/registerCollection 注册后,用 <HansIconify />icon="mdi:home" 方式渲染

目录

  • 平台与版本要求
  • 安装
  • 快速开始(静态图标组件,推荐)
  • 用法一:静态图标组件(编译期固定引用)
  • 用法二:运行时注册 + <HansIconify />(动态 icon)
  • 进阶:<HansIconifyData />(直接传 IconData)
  • API:离线注册
  • 常见问题
  • 平台注意事项
  • 生成更多静态图标(脚本)
  • 版权与许可

平台与版本要求

  • uni-app x(App):Android ✅ / iOS ✅ / Harmony ✅
  • 不支持:uni-app(vue2/vue3)/ uni-app x Web / 小程序
  • 建议环境:HBuilderX 4.31+、uni-app x 4.76+

安装

插件市场

  1. 在 HBuilderX 插件市场搜索 hans-iconify 并导入到项目
  2. 导入后目录为:uni_modules/hans-iconify/
  3. 用 HBuilderX 重新运行到目标平台(iOS/Harmony 需要重新编译/更新基座)

快速开始(静态图标组件,推荐)

为控制体积,本插件仅内置少量示例图标用于跑通 demo。实际项目中,推荐按需生成并引入 icons/<prefix>/*.uvue

# 1) 复制下方「生成更多静态图标(脚本)」里的文件到你的项目(例如放到 tools/iconify/)
# 2) 安装依赖并生成到 uni_modules/hans-iconify/icons/<prefix>/
npm --prefix tools/iconify i --no-package-lock
npm --prefix tools/iconify run iconify:popular

你也可以用自己的构建流程生成这些 .uvue 静态图标组件(只要输出结构是 icons/<prefix>/*.uvue 即可)。

<template>
    <view class="row">
        <MdiHome :size="28" color="#2D2A25" />
        <MdiCog :size="28" color="#8A8175" />
    </view>
</template>

<script setup lang="uts">
import MdiHome from "@/uni_modules/hans-iconify/icons/mdi/home.uvue"
import MdiCog from "@/uni_modules/hans-iconify/icons/mdi/cog.uvue"
</script>

用法一:静态图标组件(编译期固定引用)

适合“图标集合固定、编译期就能确定引用”的场景:直接导入某个 icon 组件即可,无需运行时注册/查表。

Props(静态图标组件)

属性 类型 默认值 必填 说明
size Number 24 等宽高尺寸(px)
color String '' 颜色(Android/iOS 使用 currentColor;Harmony 会展开为固定颜色)

Events(静态图标组件)

事件 参数 说明
load - 渲染成功
error IconifyError 渲染失败(原生渲染失败等)

用法二:运行时注册 + <HansIconify />(动态 icon)

<HansIconify /> 不内置任何图标数据:使用 icon="xxx" 方式时,必须先注册对应图标(或使用 fallbackIcon)。

建议把 registerIcon/registerCollection 放到 App.uvue / main.uts 或其他“只会执行一次”的公共模块中,避免每个页面重复注册。

<template>
    <HansIconify icon="custom:star" :size="28" color="#2D2A25" />
</template>

<script setup lang="uts">
import HansIconify from "@/uni_modules/hans-iconify/components/hans-iconify/hans-iconify.uvue"
import { registerIcon } from "@/uni_modules/hans-iconify"

registerIcon(
    "custom:star",
    24,
    24,
    '<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>'
)
</script>

Props(<HansIconify />

属性 类型 默认值 必填 说明
icon String - 图标名(例如 mdi:home
size Number 24 等宽高尺寸(px)
width Number 0 宽(px,优先级高于 size)
height Number 0 高(px,优先级高于 size)
color String '' 颜色(Android/iOS 使用 currentColor;Harmony 会展开为固定颜色)
fallbackIcon String '' 找不到 icon 时的 fallback(需要你已注册该 icon)

Events(<HansIconify />

事件 参数 说明
load - 渲染成功
error IconifyError 渲染失败(找不到 icon / 原生渲染失败)

进阶:<HansIconifyData />(直接传 IconData)

当你已经拿到了图标数据(IconData)并且不想走 registry 时,可以直接使用该组件:

<template>
    <HansIconifyData :data="data" :size="28" color="#2D2A25" />
</template>

<script setup lang="uts">
import HansIconifyData from "@/uni_modules/hans-iconify/components/hans-iconify-data/hans-iconify-data.uvue"
import { IconData } from "@/uni_modules/hans-iconify/common/icon-data.uts"

const data = new IconData(24, 24, '<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>')
</script>

Props(<HansIconifyData />

属性 类型 默认值 必填 说明
icon String '' 可选的图标名(仅用于 error 回传,便于定位)
data IconData - 图标数据
size Number 24 等宽高尺寸(px)
color String '' 颜色(Android/iOS 使用 currentColor;Harmony 会展开为固定颜色)

Events(<HansIconifyData />

事件 参数 说明
load - 渲染成功
error IconifyError 渲染失败(原生渲染失败等)

API:离线注册

推荐从模块入口导入(稳定 API):

  • registerIcon(name, width, height, body)
  • registerCollection(prefix, icons)
  • getIconData(name)

类型速览

  • IconifyError{ message: string; details?: string; icon?: string }
  • IconDatanew IconData(width: number, height: number, body: string)
  • IconEntrynew IconEntry(name: string, data: IconData)

registerIcon 说明

  • name:推荐使用 prefix:name(例如 mdi:home
  • width/height:原始 SVG 的 viewBox 尺寸(通常与 Iconify 的 width/height 一致)
  • body:SVG 内容片段(不包含最外层 <svg>),可包含多个 <path>/<g>

registerCollection 示例

<script setup lang="uts">
import { registerCollection } from "@/uni_modules/hans-iconify"
import { IconData } from "@/uni_modules/hans-iconify/common/icon-data.uts"
import { IconEntry } from "@/uni_modules/hans-iconify/common/icon-entry.uts"

registerCollection("custom", [
    new IconEntry("home", new IconData(24, 24, '<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>')),
])
</script>

常见问题

1) 图标不显示 / Icon not found

  • 确认 icon 名称完全一致(例如 mdi:home
  • 使用 <HansIconify /> 时,先完成 registerIcon/registerCollection 再渲染;或给 fallbackIcon 并确保已注册

2) iOS 标准基座下图标空白

参考下方「iOS 注意事项(看不到图标 / 空白)」:iOS 需要重新运行到 iOS 并打自定义调试基座后再验证。

3) color 不生效 / 多色图标

color 主要用于控制 currentColor。如果图标 body 内写死了 fill/stroke 或本身是多色图标,color 可能不会改变所有颜色。

平台注意事项

Android 注意事项(尺寸/清晰度)

Android 端会根据屏幕 density 生成更高分辨率的位图来渲染 SVG,以避免高分屏缩放导致的模糊/视觉差异。

Harmony 注意事项

Harmony 端使用 @ohos/svg(Canvas 渲染)作为 SVG 渲染器:

  • 依赖已在 utssdk/app-harmony/config.json 声明:@ohos/svg@2.2.2
  • 依赖库支持“多数 SVG 1.1 规范”,但仍可能存在少量标签兼容问题(例如 mask

iOS 注意事项(看不到图标 / 空白)

hans-iconify 在 iOS 端依赖 UTS 原生 + CocoaPods(SVGKit)。如果使用的是 标准基座(Simulator 里仅安装了 io.dcloud.uniappx),可能会出现:

  • undefined class: UTSSDKModulesHansIconifyHansIconifyViewByJs
  • 或 “插件不存在或者编译错误”

这时页面上会表现为图标空白。处理方式:

  • macOS 上先配置好 Xcode 环境
  • 用 HBuilderX 重新运行到 iOS(真机或重新打自定义调试基座)后再验证

生成更多静态图标(脚本)

本插件不内置大量图标数据文件;你可以用下面的离线脚本把 Iconify collection JSON 生成成静态组件:icons/<prefix>/*.uvue

下面内容是 可复制 的脚本与配置示例(建议放到你的项目根目录 tools/iconify/;如果你放到别的目录,需要自行调整配置里的 emit.uvueDir 路径)。

如果你要生成大量图标,建议把生成目录加入 .gitignore(例如 uni_modules/hans-iconify/icons/**/*.uvue),避免仓库体积膨胀。

1) 添加 tools/iconify/package.json

{
    "name": "hans-iconify-tools",
    "private": true,
    "type": "module",
    "scripts": {
        "iconify:popular": "node ./iconify-build.mjs --config ./iconify.popular.config.json"
    },
    "devDependencies": {
        "@iconify-json/bi": "*",
        "@iconify-json/lucide": "*",
        "@iconify-json/mdi": "*",
        "@iconify-json/ri": "*",
        "@iconify-json/tabler": "*"
    }
}

你也可以只安装你需要的 collection:例如只用 MDI 就只装 @iconify-json/mdi

2) 添加 tools/iconify/iconify.popular.config.json(常用 collections,全量生成,谨慎)

{
    "skipMissing": true,
    "collections": [
        {
            "package": "@iconify-json/mdi",
            "prefix": "mdi",
            "all": true,
            "emit": { "uvueDir": "../../uni_modules/hans-iconify/icons/mdi" }
        },
        {
            "package": "@iconify-json/tabler",
            "prefix": "tabler",
            "all": true,
            "emit": { "uvueDir": "../../uni_modules/hans-iconify/icons/tabler" }
        },
        {
            "package": "@iconify-json/lucide",
            "prefix": "lucide",
            "all": true,
            "emit": { "uvueDir": "../../uni_modules/hans-iconify/icons/lucide" }
        },
        {
            "package": "@iconify-json/bi",
            "prefix": "bi",
            "all": true,
            "emit": { "uvueDir": "../../uni_modules/hans-iconify/icons/bi" }
        },
        {
            "package": "@iconify-json/ri",
            "prefix": "ri",
            "all": true,
            "emit": { "uvueDir": "../../uni_modules/hans-iconify/icons/ri" }
        }
    ]
}

试跑/调试(先生成少量看看体积/效果):

npm --prefix tools/iconify i --no-package-lock
npm --prefix tools/iconify run iconify:popular -- --limit 200

3)(可选)添加 tools/iconify/iconify.config.json(按需生成)

{
    "package": "@iconify-json/mdi",
    "prefix": "mdi",
    "icons": ["home", "cog"],
    "emit": {
        "uvueDir": "../../uni_modules/hans-iconify/icons/mdi"
    }
}

4) 添加 tools/iconify/iconify-build.mjs(脚本源码)

#!/usr/bin/env node
import fs from 'node:fs/promises'
import path from 'node:path'
import { createRequire } from 'node:module'

function parseArgs(argv) {
    const args = {}
    for (let i = 0; i < argv.length; i++) {
        const token = argv[i]
        if (!token.startsWith('--')) continue
        const key = token.slice(2)
        const next = argv[i + 1]
        const hasValue = next != null && !next.startsWith('--')
        args[key] = hasValue ? next : true
        if (hasValue) i++
    }
    return args
}

function usage() {
    return [
        'Iconify collection JSON -> hans-iconify 静态图标生成器',
        '',
        '用法:',
        '  node tools/iconify/iconify-build.mjs --collection /path/to/mdi.json --icons home,cog --out-uvue-dir uni_modules/hans-iconify/icons/mdi',
        '  node tools/iconify/iconify-build.mjs --package @iconify-json/mdi --icons home,cog --out-uvue-dir uni_modules/hans-iconify/icons/mdi',
        '  node tools/iconify/iconify-build.mjs --package @iconify-json/mdi --out-uvue-dir uni_modules/hans-iconify/icons/mdi',
        '  node tools/iconify/iconify-build.mjs --config tools/iconify/iconify.config.json',
        '',
        '参数:',
        '  --config <path>         配置文件(JSON)',
        '  --collection <path>     Iconify collection JSON 文件路径(与 --package 二选一;也可在 config 里提供)',
        '  --package <name>        使用 @iconify-json/<prefix> npm 包作为数据源(与 --collection 二选一;也可在 config 里提供)',
        '  --prefix <name>         collection prefix(默认从 JSON 里读取)',
        '  --icons <a,b,c>         图标名列表(不带 prefix;默认从 config 里读取)',
        '  --all                  生成该 collection 的全部 icons(默认不含 aliases;会忽略 --icons/config.icons)',
        '  --include-aliases       与 --all 配合:额外包含 aliases 名称(可能因变换字段被跳过)',
        '  --limit <n>             与 --all 配合:限制最多生成 n 个(用于试跑/调试)',
        '  --out-uvue-dir <dir>    生成每个图标的 .uvue 文件目录(可选)',
        '',
        '配置文件:',
        '  - 单 collection:{ prefix, icons?, all?, package|collection, emit:{ uvueDir } }(默认 all=true)',
        '  - 多 collection:{ collections:[{ prefix, icons?, all?, package|collection, emit:{ uvueDir } }], skipMissing?: true }(默认每项 all=true)',
        '',
        '注意:',
        '  - 目前仅支持直接 icons 数据;带旋转/翻转等变换的 aliases 默认会跳过(或在 strict 模式下报错)。',
    ].join('\n')
}

function toSingleQuotedStringLiteral(value) {
    return (
        "'" +
        value
            .replaceAll('\\', '\\\\')
            .replaceAll("'", "\\'")
            .replaceAll('\r', '\\r')
            .replaceAll('\n', '\\n') +
        "'"
    )
}

function isPlainObject(value) {
    return value != null && typeof value === 'object' && !Array.isArray(value)
}

function normalizeIconList(iconsValue) {
    if (iconsValue == null) return []
    if (Array.isArray(iconsValue)) return iconsValue.map((v) => String(v).trim()).filter(Boolean)
    return String(iconsValue)
        .split(',')
        .map((s) => s.trim())
        .filter(Boolean)
}

function pickNumber(value, fallback) {
    return typeof value === 'number' && Number.isFinite(value) ? value : fallback
}

function resolveIconData(setJson, iconName) {
    return resolveIconDataInternal(setJson, iconName, new Set())
}

function resolveIconDataInternal(setJson, iconName, seen) {
    if (seen.has(iconName)) {
        throw new Error(`Alias loop detected at "${iconName}"`)
    }
    seen.add(iconName)

    const icons = isPlainObject(setJson.icons) ? setJson.icons : null
    if (icons != null && isPlainObject(icons[iconName])) {
        return icons[iconName]
    }

    const aliases = isPlainObject(setJson.aliases) ? setJson.aliases : null
    if (aliases != null && isPlainObject(aliases[iconName])) {
        const alias = aliases[iconName]
        const parent = typeof alias.parent === 'string' ? alias.parent : null
        if (parent == null) {
            throw new Error(`Alias "${iconName}" missing "parent"`)
        }
        const hasTransform =
            alias.rotate != null ||
            alias.hFlip != null ||
            alias.vFlip != null ||
            alias.left != null ||
            alias.top != null ||
            alias.width != null ||
            alias.height != null
        if (hasTransform) {
            throw new Error(
                `Alias "${iconName}" has transform fields; please pick its parent icon "${parent}" instead`
            )
        }
        return resolveIconDataInternal(setJson, parent, seen)
    }

    return null
}

async function ensureDir(dir) {
    await fs.mkdir(dir, { recursive: true })
}

const require = createRequire(import.meta.url)

function loadCollectionFromPackage(packageName) {
    // @iconify-json/<prefix> packages ship icons.json at package root
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const json = require(`${packageName}/icons.json`)
    return json
}

function safeFileName(name) {
    return name.replaceAll(/[^a-zA-Z0-9._-]/g, '-')
}

function renderIconUvue({ fullName, width, height, body }) {
    const lines = []
    lines.push('<template>')
    lines.push('\t<HansIconifyData')
    lines.push('\t\t:icon="ICON_NAME"')
    lines.push('\t\t:data="DATA"')
    lines.push('\t\t:size="size"')
    lines.push('\t\t:color="color"')
    lines.push('\t\t@load="onLoad"')
    lines.push('\t\t@error="onError"')
    lines.push('\t/>')
    lines.push('</template>')
    lines.push('')
    lines.push('<script setup lang="uts">')
    lines.push('import { IconData } from "@/uni_modules/hans-iconify/common/icon-data.uts"')
    lines.push('import { IconifyError } from "@/uni_modules/hans-iconify/utssdk/interface.uts"')
    lines.push(
        'import HansIconifyData from "@/uni_modules/hans-iconify/components/hans-iconify-data/hans-iconify-data.uvue"'
    )
    lines.push('')
    lines.push(`const ICON_NAME = ${toSingleQuotedStringLiteral(fullName)}`)
    lines.push(`const DATA = new IconData(${width}, ${height}, ${toSingleQuotedStringLiteral(body)})`)
    lines.push('')
    lines.push('const props = defineProps({')
    lines.push('\tsize: {')
    lines.push('\t\ttype: Number,')
    lines.push('\t\tdefault: 24,')
    lines.push('\t},')
    lines.push('\tcolor: {')
    lines.push('\t\ttype: String,')
    lines.push("\t\tdefault: '',")
    lines.push('\t},')
    lines.push('})')
    lines.push('')
    lines.push("const emit = defineEmits(['load', 'error'])")
    lines.push('')
    lines.push('const size = computed(() => props.size)')
    lines.push('const color = computed(() => props.color)')
    lines.push('')
    lines.push('function onLoad() {')
    lines.push("\temit('load')")
    lines.push('}')
    lines.push('')
    lines.push('function onError(e: IconifyError) {')
    lines.push("\temit('error', e)")
    lines.push('}')
    lines.push('</script>')
    lines.push('')
    return lines.join('\n')
}

function resolveConfigEmitUvueDir(configBaseDir, emit) {
    if (!isPlainObject(emit) || typeof emit.uvueDir !== 'string') return null
    const dir = String(emit.uvueDir)
    if (path.isAbsolute(dir)) return dir
    if (dir.startsWith('./') || dir.startsWith('../')) return path.resolve(configBaseDir, dir)
    return path.resolve(process.cwd(), dir)
}

function resolveCollectionJsonFromArgsOrConfig(args, config) {
    const collectionFromArgs = typeof args.collection === 'string'
    const collectionPathRaw = collectionFromArgs
        ? String(args.collection)
        : config != null && typeof config.collection === 'string'
            ? String(config.collection)
            : null
    const packageName =
        typeof args.package === 'string'
            ? String(args.package)
            : config != null && typeof config.package === 'string'
                ? String(config.package)
                : null
    return { collectionFromArgs, collectionPathRaw, packageName }
}

async function loadCollectionJson({ collectionFromArgs, collectionPathRaw, packageName, configBaseDir }) {
    if (collectionPathRaw != null && packageName != null) {
        throw new Error('Use either --collection or --package (not both)')
    }

    if (collectionPathRaw != null) {
        const collectionPath = path.isAbsolute(collectionPathRaw)
            ? collectionPathRaw
            : path.resolve(collectionFromArgs ? process.cwd() : configBaseDir, collectionPathRaw)
        const rawJson = await fs.readFile(collectionPath, 'utf8')
        return JSON.parse(rawJson)
    }

    if (packageName != null) {
        try {
            return loadCollectionFromPackage(packageName)
        } catch {
            throw new Error(
                `Failed to load ${packageName}/icons.json. Did you install it? (e.g. npm i -D ${packageName})`
            )
        }
    }

    return null
}

function getAllIconNamesFromCollection(setJson, { includeAliases }) {
    const names = []
    const icons = isPlainObject(setJson.icons) ? setJson.icons : null
    if (icons != null) names.push(...Object.keys(icons))
    if (includeAliases) {
        const aliases = isPlainObject(setJson.aliases) ? setJson.aliases : null
        if (aliases != null) names.push(...Object.keys(aliases))
    }
    return Array.from(new Set(names)).sort()
}

function pickBoolean(value) {
    return value === true
}

function pickLimit(value) {
    if (typeof value === 'number' && Number.isFinite(value) && value > 0) return Math.floor(value)
    if (typeof value === 'string') {
        const n = Number.parseInt(value, 10)
        if (Number.isFinite(n) && n > 0) return n
    }
    return null
}

async function generateUvueFromCollection({
    setJson,
    prefix,
    icons,
    outUvueDir,
    skipMissing,
    skipInvalid,
}) {
    const defaultWidth = pickNumber(setJson.width, 24)
    const defaultHeight = pickNumber(setJson.height, 24)

    const entries = []
    const missing = []
    const invalid = []
    for (const iconName of icons) {
        let iconData = null
        try {
            iconData = resolveIconData(setJson, iconName)
        } catch (e) {
            if (skipInvalid) {
                invalid.push(iconName)
                continue
            }
            throw e
        }
        if (iconData == null) {
            missing.push(iconName)
            continue
        }
        const body = typeof iconData.body === 'string' ? iconData.body : null
        if (body == null || body.length === 0) {
            missing.push(iconName)
            continue
        }
        const width = pickNumber(iconData.width, defaultWidth)
        const height = pickNumber(iconData.height, defaultHeight)
        entries.push({
            iconName,
            fullName: `${prefix}:${iconName}`,
            width,
            height,
            body,
        })
    }

    if (missing.length > 0) {
        if (skipMissing) {
            console.warn(`Warn: missing icons for ${prefix}: ${missing.join(', ')}`)
        } else {
            throw new Error(`Icon(s) not found in ${prefix}: ${missing.join(', ')}`)
        }
    }

    if (invalid.length > 0) {
        if (skipInvalid) {
            console.warn(`Warn: skipped invalid alias icons for ${prefix}: ${invalid.join(', ')}`)
        } else {
            throw new Error(`Invalid alias icon(s) in ${prefix}: ${invalid.join(', ')}`)
        }
    }

    if (outUvueDir == null) return
    await ensureDir(outUvueDir)
    for (const entry of entries) {
        const fileName = `${safeFileName(entry.iconName)}.uvue`
        const filePath = path.join(outUvueDir, fileName)
        await fs.writeFile(filePath, renderIconUvue(entry), 'utf8')
    }
}

async function main() {
    const args = parseArgs(process.argv.slice(2))
    if (args.help || args.h) {
        console.log(usage())
        process.exit(0)
    }

    let config = null
    let configBaseDir = process.cwd()
    if (typeof args.config === 'string') {
        configBaseDir = path.dirname(path.resolve(String(args.config)))
        const raw = await fs.readFile(args.config, 'utf8')
        config = JSON.parse(raw)
    }

    const outUvueDir =
        typeof args['out-uvue-dir'] === 'string'
            ? args['out-uvue-dir']
            : resolveConfigEmitUvueDir(configBaseDir, config != null ? config.emit : null)

    const configSkipMissing = config != null && config.skipMissing === true
    const collections = config != null && Array.isArray(config.collections) ? config.collections : null
    if (collections != null) {
        const globalAll = pickBoolean(args.all)
        const globalIncludeAliases = pickBoolean(args['include-aliases'])
        const globalLimit = pickLimit(args.limit)
        for (const item of collections) {
            if (!isPlainObject(item)) continue
            const itemPrefix = typeof item.prefix === 'string' ? String(item.prefix) : null
            if (itemPrefix == null || itemPrefix.trim().length === 0) {
                throw new Error('Invalid collections item: missing prefix')
            }
            const itemOutDir = resolveConfigEmitUvueDir(configBaseDir, item.emit)
            const { collectionFromArgs, collectionPathRaw, packageName } = resolveCollectionJsonFromArgsOrConfig(
                {},
                item
            )
            const setJson = await loadCollectionJson({
                collectionFromArgs,
                collectionPathRaw,
                packageName,
                configBaseDir,
            })
            if (setJson == null) throw new Error(`Invalid collections item: missing package/collection for ${itemPrefix}`)
            const prefixFromJson = typeof setJson.prefix === 'string' ? setJson.prefix : null
            const effectivePrefix = itemPrefix.trim()
            if (prefixFromJson != null && prefixFromJson.trim().length > 0 && prefixFromJson != effectivePrefix) {
                console.warn(`Warn: prefix mismatch. config=${effectivePrefix} json=${prefixFromJson}`)
            }

            const itemAll = pickBoolean(item.all) || globalAll
            const itemIncludeAliases = pickBoolean(item['include-aliases']) || globalIncludeAliases
            const itemLimit = pickLimit(item.limit) ?? globalLimit
            const explicitIcons = normalizeIconList(item.icons)
            const effectiveAll = itemAll || explicitIcons.length === 0
            const itemIcons = effectiveAll
                ? getAllIconNamesFromCollection(setJson, { includeAliases: itemIncludeAliases })
                : explicitIcons
            if (itemIcons.length === 0) throw new Error(`Invalid collections item: no icons found for ${effectivePrefix}`)
            const effectiveIcons = itemLimit != null ? itemIcons.slice(0, itemLimit) : itemIcons

            await generateUvueFromCollection({
                setJson,
                prefix: effectivePrefix,
                icons: effectiveIcons,
                outUvueDir: itemOutDir,
                skipMissing: configSkipMissing,
                skipInvalid: effectiveAll || configSkipMissing,
            })
            console.log(
                `Done. prefix=${effectivePrefix} icons=${effectiveIcons.length}` +
                    (itemOutDir ? ` uvueDir=${itemOutDir}` : '') +
                    (effectiveAll ? ' all=true' : '')
            )
        }
        return
    }

    const { collectionFromArgs, collectionPathRaw, packageName } = resolveCollectionJsonFromArgsOrConfig(args, config)
    const setJson = await loadCollectionJson({ collectionFromArgs, collectionPathRaw, packageName, configBaseDir })
    if (setJson == null) {
        console.error('Missing --collection or --package')
        console.error('')
        console.error(usage())
        process.exit(1)
    }

    const prefix =
        typeof args.prefix === 'string'
            ? args.prefix
            : config != null && typeof config.prefix === 'string'
                ? config.prefix
                : typeof setJson.prefix === 'string'
                    ? setJson.prefix
                    : null
    if (prefix == null || prefix.trim().length === 0) {
        throw new Error('Missing prefix (use --prefix or provide "prefix" in collection JSON)')
    }

    const icons = (() => {
        const includeAliases = pickBoolean(args['include-aliases'])
        const limit = pickLimit(args.limit)
        const all = pickBoolean(args.all) || (config != null && pickBoolean(config.all))
        if (all) {
            const allIcons = getAllIconNamesFromCollection(setJson, { includeAliases })
            return limit != null ? allIcons.slice(0, limit) : allIcons
        }
        if (typeof args.icons === 'string') return normalizeIconList(args.icons)
        if (config != null) {
            const configIcons = normalizeIconList(config.icons)
            if (configIcons.length > 0) return configIcons
        }
        const allIcons = getAllIconNamesFromCollection(setJson, { includeAliases })
        return limit != null ? allIcons.slice(0, limit) : allIcons
    })()
    if (icons.length === 0) {
        throw new Error('No icons found in collection JSON')
    }

    await generateUvueFromCollection({
        setJson,
        prefix: prefix.trim(),
        icons,
        outUvueDir,
        skipMissing: false,
        skipInvalid: pickBoolean(args.all) || (config != null && pickBoolean(config.all)),
    })

    console.log(
        `Done. prefix=${prefix.trim()} icons=${icons.length}` + (outUvueDir != null ? ` uvueDir=${outUvueDir}` : '')
    )
}

main().catch((err) => {
    console.error(err instanceof Error ? err.message : String(err))
    process.exit(1)
})

参数说明(完整)

node tools/iconify/iconify-build.mjs --help

  • --config <path>:配置文件(JSON)
  • --collection <path>:Iconify collection JSON 文件(与 --package 二选一)
  • --package <name>:使用 @iconify-json/<prefix> 作为数据源(与 --collection 二选一)
  • --prefix <name>:collection prefix(默认从 JSON 里读)
  • --icons <a,b,c>:图标名列表(不带 prefix;默认从 config 里读)
  • --all:生成该 collection 的全部 icons(默认不含 aliases;会忽略 --icons/config.icons
  • --include-aliases:与 --all 配合,额外包含 aliases 名称(带变换的 aliases 可能被跳过/报错)
  • --limit <n>:与 --all 配合,最多生成 n 个(试跑用)
  • --out-uvue-dir <dir>:输出目录(每个图标一个 .uvue 文件)

版权与许可

  • 本插件只负责“渲染与离线注册”,不提供线上拉取服务,也不默认内置大规模图标数据。
  • 不同 Iconify collection(@iconify-json/*)对应的图标有各自的许可证与署名要求;请在你的产品中按所选图标集的许可条款使用与声明。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。