头图

vite-plugin-iconify - 在 Vue3 + Vite2 按需使用 SVG 图标的另一种方式

需要最后达到的使用目的

<!-- 会替换成 :icon="defineAsyncComponent(() => import('~icons/mdi/account'))" 即 mdi 库的 account 图标组件 -->
<v-icon icon="mdi-account" />

<!-- 会替换成 :icon="defineAsyncComponent(() => import('~icons/dashboard'))" 即本地 src/icons/dashboard.svg 图标组件 -->
<v-icon icon="dashboard" />

<!-- 会替换成 :prepend-icon="defineAsyncComponent(() => import('~icons/mdi/arrow-up-bold-circle'))" 即 mdi 库的 arrow-up-bold-circle 图标组件 -->
<v-button prepend-icon="mdi-arrow-up-bold-circle">按钮</v-button>

以下为原理介绍,如跳过直接查看 vite-plugin-iconify

先创建一个 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.ts
import { 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

// Utils
import { inject } from 'vue'

// Types
import 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 中全局注册提供者

// Utils
import { createApp } from 'vue'
import App from './App.vue'

// Icons
import icons from '~icons'

// Compositions
import { 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

66 声望
1 粉丝
0 条评论
推荐阅读
dom-vcr - 使用 HTML5 canvas 和 SVG 从 DOM 节点生成视频或 GIF
📦 安装 {代码...} 🦄 使用录制 2s 生成 4 帧 GIF需要安装 gif.js {代码...} 手动加帧生成 GIF需要安装 gif.js {代码...} CDN {代码...} github [链接]

weng阅读 759

🖼️ 如何解决 SVG 图片中字体失效的问题
如果你喜欢我的文章,希望点赞👍 收藏 📁 评论 💬 三连支持一下,谢谢你,这对我真的很重要!「SVG 图片中字体失效」的修复方案很简单,只想看答案翻到最后看结论就行。如果想看我的排查思路和具体原因可以从头开始...

卤代烃6阅读 1.5k

【原理揭秘】Vite 是怎么兼容老旧浏览器的?你以为仅仅依靠 Babel?
对前端开发者来说,Vite 应该不算陌生了,它是一款基于 nobundle 和 bundleless 思想诞生的前端开发与构建工具,官网对它的概括和期待只有一句话:“下一代的前端工具链”。

京东云开发者6阅读 728

封面图
Vite开发环境搭建
Vite现在可谓是炙手可热,可能很多小伙伴还没有使用过Vite,但是我相信大多数小伙伴已经在使用Vite了,因为是太香了有没有。可能在使用过程中很多东西Vite不是配置好的,并不像Vue-cli配置的很周全,那么今天就说...

Aaron7阅读 5.8k

写一个Vue DevTools,让开发体验飞一会
近年来,人们越来越关注开发者体验 (DX)。工具和框架也一直在努力改进 DX,比如这两年光速发展的Vite。在大多数人的印象中,Vite的特点是快,但是在我看来让它发展迅速并在前端构建工具占据一席之地的主要原因是...

null仔2阅读 942

封面图
Grow Admin 中后台框架简介
Grow Admin是开源的中后台模版,使用目前较新的主流技术栈开发,是一款开箱即用的中台前端/设计解决方案,具有轻松构建规范且美观的系统,丰富的布局模式,具有高可配性,满足您的各类布局需求。

Aaron2阅读 678

封面图
svg之viewBox
基本语法首先,我在大小为400 x 400的画布中绘制了一个200 x 200的矩形:可以发现,没有问题。现在,我把svg的宽和高改变后:可以看见,等比例变小了。简单的理解就是:viewBox规范了画布的大小,svg标签里面的内...

zxl200707013阅读 639

封面图
66 声望
1 粉丝
宣传栏