如何编写一个vite处理svg的插件

手动实现一个vite自动处理svg的插件

vite插件APIl

import { IndexHtmlTransformResult, Plugin } from 'vite'
import { Dirent, readdirSync, readFileSync } from 'fs'
import { join } from 'path'

class SvgPlugin implements Plugin {
    private readonly _path: string[] | string
    private readonly _prefix: string = 'icon'
    private readonly _svgTitle: RegExp = /<svg([^>+].*?)>/
    private readonly _clearHeightWidth: RegExp = /(width|height)="([^>+].*?)"/g
    private readonly _hasViewBox: RegExp = /(viewBox="[^>+].*?")/g
    private readonly _clearReturn: RegExp = /(\r)|(\n)/g

    private _svgResult: Map<string, { value: string, url: string }> = new Map()

    public name = 'svg-plugin'

    public get svgResult (): string[] {
        const result: string[] = []
        this._svgResult.forEach(({ value }) => result.push(value))
        return result
    }

    constructor (path: string[] | string, prefix: string) {
        this._path = path
        this._prefix = prefix
        if (typeof this._path === 'string') {
            this.addSvgFile(this._path)
        } else {
            this._path.forEach((it: string) => this.addSvgFile(it))
        }
    }

    private _replaceSvg = (name: string): string => {
        const suffix: string = name.split('.').pop() ?? ''
        return name.replace(`.${ suffix }`, '')
    }

    public addSvgFile (path: string): void | never {
        // withFileTypes: true the result data will be an array of Dirent.
        const directs: Dirent[] = readdirSync(path, { withFileTypes: true })
        directs.forEach((it: Dirent) => {
            if (it.isDirectory()) {
                this.addSvgFile(join(path, it.name, '/'))
            }
            const currentPath: string = join(path, it.name)
            if (!currentPath.toLocaleLowerCase().endsWith('.svg')) return

            // 校验重名。大小写不一致也算重复
            const name = this._replaceSvg(it.name).toLocaleLowerCase()
            if (this._svgResult.has(name)) {
                const item: UndefinedAble<{ value: string, url: string }> = this._svgResult.get(name)
                throw new Error(`存在与${ it.name }名称一样的svg图片。当前svgURL:${ currentPath },重复svgURL:${ item && item.url }`)
            }

            const svg: string = readFileSync(currentPath)
                .toString()
                .replace(this._clearReturn, '')
                .replace(this._svgTitle, ($1: string, $2: string): string => {
                    let width = '0'
                    let height = '0'
                    let content = $2.replace(
                        this._clearHeightWidth,
                        (s1: string, s2: string, s3: string): string => {
                            if (s2 === 'width') {
                                width = s3
                            } else if (s2 === 'height') {
                                height = s3
                            }
                            return ''
                        }
                    )
                    if (!this._hasViewBox.test($2)) {
                        content += `viewBox="0 0 ${ width } ${ height }"`
                    }
                    /**
                     * <symbol>的id属性定义了符号的唯一名称。
                     * id属性是<symbol>元素所必需的, 因此以后可以由<use>元素引用。
                     */
                    return `<symbol id="${ this._prefix }-${ it.name.replace(
                        '.svg',
                        ''
                    ) }" ${ content }>`
                }).replace('</svg>', '</symbol>')
            this._svgResult.set(name, {
                value: svg,
                url: currentPath
            })
        })
    }

    public transformIndexHtml (html: string): IndexHtmlTransformResult {
        // 添加svg元素
        return html.replace(
            '<body>',
            `
          <body>
            <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
              ${ this.svgResult.join('') }
            </svg>
        `
        )
    }

}

/**
 * 处理svg插件
 * @param path 需要解析的路径,可以是数组/字符串。可以为文件夹或者具体某个svg图片
 * @param prefix 标识的前缀。默认为 svg
 */
export default function vitePluginSvgResolve (path: string[] | string, prefix = 'svg'): Plugin {
    const plugin: SvgPlugin = new SvgPlugin(path, prefix)
    return {
        name: plugin.name,
        // 处理index.html的时候替生成svg
        transformIndexHtml: (html: string): IndexHtmlTransformResult => plugin.transformIndexHtml(html)
    }
}

注册插件

  1. 在vite.config.ts中导入并且使用
import vitePluginSvgResolve from './src/plugin/vitePluginSvgResolve'

//.... 在plugins选项中注册即可
plugins: [
    vue(),
    // 传入路径即可。可以传递多个或者单个
    vitePluginSvgResolve(['./src/assets/svg', './src/module/page/views/assets/images/componentSvg'])
    // 可以传入单个svg文件夹
    // vitePluginSvgResolve('./src/assets/svg')
    // 可以传入svg文件(也支持多个)
    // vitePluginSvgResolve('./src/assets/svg/test.svg')

],
//....

生成svg组件。并且注册到全局

  1. 生成svg组件
<template>
  <svg :class="svgClass" v-bind="$attrs" >
    <use :xlink:href="iconName"/>
  </svg>
</template>

<script lang="ts" setup>

import { defineProps, computed } from 'vue'
const props = defineProps({
    name: {
        type: String,
        required: true
    },
    /**
     * 以下属性都是为了方便使用,不是必须的,可以直接把svg-icon当作字体来修改颜色大小等效果
     * 当作字体图标的前提是svg的填充色是currentColor,如果不是可以手动修改填充为currentColor
     * 一些如节点图标,文件夹,多色图标就不用改currentColor,因为一般他们也不会被修改颜色,改了currentColor之前的填充色会没有,默认黑色
     * **/
    /**
     * 如果ui给的svg不符合要求(过大,很多重复元素比入<g>,一般是比较简洁的)
     * 可以全局安装svgo来压缩图片
     **/

    /**
     * 如果遇到图标是字体图标(就是有currentColor的)要保留原来图标颜色,但不好单独设置颜色(比如对象树是批量渲染的),
     * 这就可以在svgColor的文件中以svg名为key值,来设置颜色
     */
    // 主要用size
    size: {
        type: String
    },
    // 图标颜色
    color: {
        type: String,
        default: ''
    },
    // 图标的hover颜色
    hoverColor: {
        type: String,
        default: ''
    },
    rotateZ: {
        default: '0deg'
    },
    rotateY: {
        default: '0deg'
    }
})

const iconName = computed(()=>`#icon-${props.name}`)
const svgClass = computed(()=> {
    if (props.name) {
        return `svg-icon icon-${props.name}`
    }
    return 'svg-icon'
})
</script>

// 手动加上name
<script lang='ts'>
import { defineComponent } from 'vue'
export default defineComponent({ name: 'svg-icon' })
</script>

<style lang='scss' model>
.svg-icon {
  width: v-bind("props.size || '1em'");
  height: v-bind("props.size || '1em'");;
  fill: currentColor;
  vertical-align: middle;
  color: v-bind('props.color');
  transform: rotateZ(v-bind('props.rotateZ')) rotateY(v-bind('props.rotateY'));
  &:hover{
      color: v-bind('props.hoverColor || props.color');
  }
}
</style>
  1. 全局注册。在main.ts中注册即可
import svgIcon from './src/globComponent/svg-icon.vue'
//...
app.component('svg-icon', svgIcon)
  1. 使用
<template>
	<!-- name为svg的名称 -->
  	<svg-icon name="tsest"></svg-icon>
</template>
vite一个现代化的前端构建工具,用于快速构建Vue项目。它支持使用插件来加载和处理各种文件类型,包括SVG图标文件。 在使用vite构建Vue项目时,你可以通过安装vite-svg-loader插件,并在vite.config.js中进行相关配置来处理SVG图标文件。你需要指定需要缓存的SVG图标文件夹,并设置symbolId格式,这个格式与在引入SVG组件时的name配置项有关。 然后,你可以在项目中创建一个文件夹,并将SVG图标放入其中。接下来,在main.js文件中引入并注册SvgIcon组件。这个组件可以显示SVG图标,你可以通过传入name、prefix和color属性来自定义图标的名称、前缀和颜色。 最后,全局注册SvgIcon组件,这样你就可以在项目的任何地方使用它来显示SVG图标了。 综上所述,vite可以很方便地处理和使用SVG图标文件,并且通过SvgIcon组件可以轻松地在Vue项目中显示这些图标。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [vite-svg-loader:Vite 2.x插件可将SVG文件作为Vue组件加载](https://download.csdn.net/download/weixin_42176827/18659286)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [vue3+vite配置svg文件的全局使用(想怎么改颜色、宽高都可以)](https://blog.csdn.net/m0_38134431/article/details/125508459)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值