手动实现一个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 {
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 }"`
}
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 {
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>
`
)
}
}
export default function vitePluginSvgResolve (path: string[] | string, prefix = 'svg'): Plugin {
const plugin: SvgPlugin = new SvgPlugin(path, prefix)
return {
name: plugin.name,
transformIndexHtml: (html: string): IndexHtmlTransformResult => plugin.transformIndexHtml(html)
}
}
注册插件
- 在vite.config.ts中导入并且使用
import vitePluginSvgResolve from './src/plugin/vitePluginSvgResolve'
plugins: [
vue(),
vitePluginSvgResolve(['./src/assets/svg', './src/module/page/views/assets/images/componentSvg'])
],
生成svg组件。并且注册到全局
- 生成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
},
size: {
type: String
},
color: {
type: String,
default: ''
},
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>
- 全局注册。在main.ts中注册即可
import svgIcon from './src/globComponent/svg-icon.vue'
app.component('svg-icon', svgIcon)
- 使用
<template>
<svg-icon name="tsest"></svg-icon>
</template>