@dyrily/svg-convert
Version:
将svg图标转成vue组件。
154 lines (140 loc) • 4.82 kB
text/typescript
import type { ICLIArgs } from './cli'
import { join, parse, resolve } from 'node:path'
import { XMLBuilder, XMLParser } from 'fast-xml-parser'
import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
const dirname = process.cwd()
const xmlAttrsPrefix = '@_'
const includeAttrs = ['viewBox', 'fill', 'stroke'].map((t) => `${xmlAttrsPrefix}${t}`)
const includeTags = ['svg', 'circle', 'ellipse', 'rect', 'line', 'polyline', 'polygon', 'path', 'text', 'tspan', 'g', 'defs', 'use', 'linearGradient', 'pattern', 'mask', 'clipPath', 'filter']
interface Component {
template: string
filename: string
outPath: string
output: boolean
}
type Plugin = {
loaded?: (svg: Record<string, any>) => any
beforeOutput?: (app: App, component: Component) => any
end?: (app: App) => any
[key: string]: any
}
class App {
declare options: ICLIArgs
declare plugins: Plugin[]
declare parser: XMLParser
declare builder: XMLBuilder
declare svgKey: string[]
constructor(options: ICLIArgs) {
const xmlOpt = { ignoreAttributes: false }
this.options = options
this.plugins = []
this.parser = new XMLParser(xmlOpt)
this.builder = new XMLBuilder(xmlOpt)
this.svgKey = [...includeAttrs, ...includeTags]
}
use(plugin: Plugin) {
this.plugins.push(plugin)
return this
}
async start() {
const dest = resolve(dirname, this.options.dest)
const svgDir = resolve(dirname, this.options.src)
const svgList = await readdir(svgDir)
await mkdir(dest, { recursive: true })
for (let svgName of svgList) {
const filepath = resolve(svgDir, svgName)
const path = parse(filepath)
if (path.ext !== '.svg') break
const data = await readFile(filepath)
const xml = this.parser.parse(data)
const svg: Record<string, any> = {}
Object.keys(xml['svg']).forEach((key) => this.svgKey.includes(key) && Reflect.set(svg, key, xml['svg'][key]))
for (const plugin of this.plugins) {
await plugin.loaded?.(svg)
}
const component: Component = {
template: this.builder.build({ template: { svg } }),
filename: `${this.options.prefix}${path.name}.vue`,
outPath: '',
output: true,
}
component.outPath = resolve(dest, component.filename)
for (const plugin of this.plugins) {
await plugin.beforeOutput?.(this, component)
}
if (component.output) {
await writeFile(component.outPath, component.template)
}
}
for (const plugin of this.plugins) {
await plugin.end?.(this)
}
}
}
const corePlugin: Plugin = {
loaded(svg) {
svg[`${xmlAttrsPrefix}class`] = 'iconfont-svg'
},
async beforeOutput(app, component) {
if (!app.options.colorful) {
const matchFill = component.template.match(/ fill="[^"]*"/g)
debugger
if (matchFill?.length) {
for (const item of matchFill) {
if (item == ' fill="none"') continue
component.template = component.template.replace(item, ' fill="currentColor"')
}
}
const matchStroke = component.template.match(/ stroke="[^"]*"/g)
if (matchStroke?.length) {
for (const item of matchStroke) {
if (item == ' stroke="none"') continue
component.template = component.template.replace(item, ' stroke="currentColor"')
}
}
// component.template = component.template.replace(/ fill="[^"]*"/g, ' fill="currentColor"')
// component.template = component.template.replace(/ stroke="[^"]*"/g, ' fill="currentColor"')
}
if (!app.options.allowOverride) {
try {
await access(component.outPath)
component.output = false
console.log(`${component.filename}已存在`)
} catch {}
}
},
}
// 生成类型文件
const typePlugin: Plugin = {
async end(app) {
const options = app.options
if (!options.type) return
const filepath = resolve(dirname, options.type)
await mkdir(resolve(filepath, '..'), { recursive: true })
const dest = resolve(dirname, options.dest)
const compList = await readdir(dest)
const typeFile = `// 自动生成文件,请勿修改
declare module '@vue/runtime-core' {
export interface GlobalComponents {
${compList
.map((t) => {
const name = parse(t)
.name.replace(/(^\S|-\S)/g, (str) => str.toUpperCase())
.replace(/-/g, '')
const fp = join(options.dest, t)
.replace(/\\/g, '/')
.replace(/^src\//, '@/')
return [name, `typeof import('${fp}')`].join(': ')
})
.join('\n ')}
}
}
export {}`
await writeFile(filepath, typeFile)
},
}
export function createApp(options: ICLIArgs) {
const instance = new App(options)
instance.use(corePlugin).use(typePlugin)
return instance
}