须要最初达到的应用目标

<!-- 引入 mdi 下的 account 图标作为图标 --><v-icon icon="mdi-account" /><!-- 引入 src/icons/dashboard.svg 自定义图标作为图标 --><v-icon icon="dashboard" /><!-- 引入 mdi 下的 arrow-up-bold-circle 图标作为前置按钮图标 --><v-button prepend-icon="mdi-arrow-up-bold-circle">按钮</v-button>

VIcon 组件

假如咱们首先封装一个 v-icon 组件在 src/components/VIcon.vue

<template>  <i class="v-icon">    <slot />  </i></template><style scoped>  .v-icon {    display: inline-block;    width: 1em;    height: 1em;    font-size: 1em;  }  .v-icon > svg {    width: 100%;    height: 100%;  }</style>

应用 unplugin-icons 作为图标按需加载

在目前的解决方案中,可能会应用 unplugin-icons 插件在 SFC 中动态按需加载图标。

<script setup>  import IconAccessibility from '~icons/carbon/accessibility'</script><template>  <v-icon>    <icon-accessibility />  </v-icon></template>

配合 unplugin-vue-components 主动导入省去 scriptimport 局部。

<template>  <v-icon>    <i-carbon-accessibility />  </v-icon></template>

这在单纯应用 v-icon 组件的时候没什么问题,然而如果基于 v-icon 封装其余组件,再应用就会有点繁琐。

例如咱们封装一个 v-buttonsrc/components/VButton.vue ,并增加一个前置图标的插槽,代码如:

<template>  <button class="v-button">    <v-icon v-if="$slots['prepend-icon']">      <slot name="prepend-icon" />    </v-icon>    <span><slot /></span>  </button></template>

咱们的应用形式如下:

<template>  <v-button>    <template #prepend-icon>      <i-carbon-accessibility />    </template>    按钮  </v-button></template>

如果 v-button 在嵌套在别的父组件中,应用这个父组件时传递一个按钮的前置图标将会十分繁琐,所以我可能更想这样去应用:

<template>  <v-button prepend-icon="carbon-accessibility">按钮</v-button></template>

如何转化成这样去用呢,最简略的解决办法是将 v-button 属性 prepend-icon 的动态值 "carbon-accessibility" 字符串替换成 <i-carbon-accessibility /> 组件导入。

如何做模板组件属性值的动态替换

咱们能够在 @vue/compiler-sfc 模板编译后的代码中,通过正则替换文本值,例如模板代码为:

<script setup>  import VButton from './components/VButton.vue'</script><template>  <v-button prepend-icon="mdi-delete">按钮</v-button></template>

模板会编译成

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openBlock(),    _createBlock($setup["VButton"], {      "prepend-icon": "mdi-delete"    }, {      default: _withCtx(()=>[_hoisted_1]),      _: 1 /* STABLE */    }))}

通过正则

/_create(?:VNode|Block)\((?:_component_v_button|\$setup\["VButton"\]), .*?{.*?("prepend-icon"): (.+?)([,|}].*?\))/gs

匹配出动态局部,再转化为

import __icons_0 from '~icons/mdi/delete'function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openBlock(),    _createBlock($setup["VButton"], {      "prepend-icon": __icons_0    }, {      default: _withCtx(()=>[_hoisted_1]),      _: 1 /* STABLE */    }))}

就实现了组件属性值从字符串转化为传入一个组件。

应用 vite-plugin-iconify 插件

这一过程我封装了一个 vite-plugin-iconify 插件,如上述只须要配置 replaceableProps

// vite.config.tsimport { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import Iconify from 'vite-plugin-iconify'// https://vitejs.dev/config/export default defineConfig({  plugins: [    vue(),    Iconify({      replaceableProps: [        { component: 'VButton', props: ['prependIcon'] },        { component: 'VIcon', props: ['icon'] },      ]    }),  ]})

革新组件 v-icon 以反对属性值传入组件

<script setup lang="ts">  defineProps<{ icon?: object }>()</script><template>  <i class="v-icon">    <slot>      <component :is="icon" />    </slot>  </i></template><style scoped>  .v-icon {    display: inline-block;    width: 1em;    height: 1em;    font-size: 1em;  }  .v-icon > svg {    width: 100%;    height: 100%;  }</style>

革新组件 v-button 以反对属性值传入组件

<script setup lang="ts">  import VIcon from './VIcon.vue'  defineProps<{ prependIcon?: object }>()</script><template>  <button class="v-button">    <v-icon v-if="$slots['prepend-icon'] || prependIcon" :icon="prependIcon">      <slot name="prepend-icon" />    </v-icon>    <span><slot /></span>  </button></template><style>  .v-button {    display: inline-flex;    align-items: center;  }</style>

就能够间接在模板中应用

<script setup>  import VButton from './components/VButton.vue'  import VIcon from './components/VIcon.vue'</script><template>  <v-icon icon="mdi-delete" />  <v-button prepend-icon="mdi-delete">按钮</v-button></template>

依据文件目录导入自定义图标

在以往的习惯用法中,咱们通常喜爱下载 svg 文件到某个目录作为自定义图标载入,vite-plugin-iconify 也反对了,默认会载入 src/icons 目录中 .svg 后缀的文件,通过 ~icons 导入组件集。

那如何将自定义图标联合 v-icon 应用呢,新增文件如下:

// src/icons/dashboard.svg<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M168.106667 621.44l120.746666 57.962667 223.274667 108.138666 215.317333-104.32 128.768-61.674666a64 64 0 0 1-29.952 84.970666l-286.229333 138.624a64 64 0 0 1-55.808 0L197.994667 706.517333A64 64 0 0 1 168.106667 621.44z m687.829333-133.930667a64 64 0 0 1-29.674667 85.546667L540.010667 711.68a64 64 0 0 1-55.808 0L197.994667 573.056A64 64 0 0 1 166.826667 490.88l317.013333 149.525333 28.288 13.696 286.229333-138.624-0.149333-0.064 57.728-27.882666zM540.032 185.792l286.208 138.602667a64 64 0 0 1 0 115.2l-286.208 138.624a64 64 0 0 1-55.808 0L197.994667 439.594667a64 64 0 0 1 0-115.2L484.224 185.813333a64 64 0 0 1 55.808 0z m-27.904 57.6l-286.229333 138.602667 286.229333 138.624 286.229333-138.624-286.229333-138.602667z"></path></svg>

咱们想通过简略指定 icon="dashboard" 就能应用

<v-icon icon="dashboard" />

这里提供一个应用形式

首先咱们创立一个 icons 的组合式 API

// src/compositions/icons.ts// Utilsimport { inject } from 'vue'// Typesimport type { InjectionKey, DefineComponent } from 'vue'export interface IconsInstance{  /**   * @zh 所有图标的别名   */  aliases: Record<string, DefineComponent>}export const IconsKey: InjectionKey<IconsInstance> = Symbol.for('app:icons')/** * @zh 创立图标集 * * @param options */export function createIcons (options: IconsInstance) {  return options}/** * @zh 应用图标集 */export function useIcons () {  const icons = inject(IconsKey)  if (!icons) throw new Error('Could not find icons instance')  return icons}

main.ts 中全局注册提供者

// Utilsimport { createApp } from 'vue'import App from './App.vue'// Iconsimport icons from '~icons'// Compositionsimport { createIcons, IconsKey } from './compositions/icons'const app = createApp(App)app.provide(IconsKey, createIcons({  aliases: icons}))app.mount('#app')

批改 v-icon 组件如下

<script setup lang="ts">  // src/components/VIcon.vue    // Utils  import { computed } from 'vue'  // Compositions  import { useIcons } from '../compositions/icons'  const props = defineProps<{    icon?: string | object  }>()  const icons = useIcons()  const componentIcon = computed(() => {    if (!props.icon) return    if (typeof props.icon === 'string' && props.icon in icons.aliases) {      return icons.aliases[props.icon]    }    return props.icon  })</script><template>  <i class="v-icon">    <slot>      <component :is="componentIcon" />    </slot>  </i></template><style scoped>  .v-icon {    display: inline-block;    width: 1em;    height: 1em;    font-size: 1em;  }  .v-icon > svg {    width: 100%;    height: 100%;  }</style>

你就能够欢快的这样应用了

<v-icon icon="dashboard" />

示例代码

示例残缺代码查看 examples/vite-vue3 ,具体配置和用法查看 vite-plugin-iconify 。