Vue3 + Svg 一步步实现自己的图标库(提供源码)
前言
在看 antdv 、naive-ui 等组件库源码的时候,会发现它们都将组件库的图标单独抽成了一个 npm 包,并且可以通过组件的形式来引入,由于是通过组件的形式来引入, svg 以 dom 的形式渲染在浏览器上,相对于字体图标来说可以减少 http 请求,在低端设备上 svg 有更好的清晰度等优点。那么这些 svg 图标库是怎么创建的呢?我们一起往下看吧。
实战操作
一些准备
首先使用 vite
来搭建一个工程,包管理器选择了 pnpm
。然后在工程目录的 src 文件夹下创建一个 svg 文件夹,用于存放设计师给的 svg 文件,当然,为了学习,大家也可以到 iconfont 上面下载免费的 svg 图标。
接下来就要下载一些关键依赖,主要是以下依赖:
-
css-render ,一个实现 CSS-in-JS 的库,挺好用的。
-
execa,增强了 nodejs 子进程 child_process 的方法,相对于 nodejs 原生的子进程,该 API 使用起来更友好。
-
fs-extra,添加了原生
fs
模块中未包含的文件系统方法,并为fs
模块的方法添加了Promise
支持,使用起来更加友好。 -
v2s,将 Vue 转换成 script 。以可摇树优化的方式,将 vue 文件的 template 与 script 转换为 ts 、js 文件,也是很好用的。
项目中其它的依赖就略过了,相对比较简单,大家可自身在 github 或 npm 中搜索学习。
安装完项目的依赖后,就可以编写生成 svg 图标的脚本了,该脚本的实现也是本项目的核心。
脚本实现
首先在 package.json 文件的 scripts 实现两个脚本命令
{
"scripts": {
"clean": "rimraf lib es dist",
"build:icons": "pnpm run clean && node ./scripts/build.js"
},
}
rimraf 是跨平台实现删除文件、文件夹的库,用于代替 linux 中的 rm -rf
命令。在执行构建图标的命令前将之前的构建结果删掉,可以保证每次的构建结果都是干净的,不受上次构建结果的影响。
接下来项目根目录下创建 scripts 文件夹,然后在 scripts 文件夹下创建 build.js 文件,用于编写构建脚本 。
由于 v2s
库依赖了 vue-template-compiler
。
而我们的工程中依赖了 Vue3 ,Vue3 与 vue-template-compiler
同时在依赖列表中会报版本冲突的错误:
会报错的原因在于 vue-template-compiler
中的一段代码,它会取当前自己的版本与 Vue 的版本对比,如果不一致,则会报版本不一致的错误:
所以在 scripts\build.js
中开头有这么一段代码:
// make sure vue template compiler do not throw error
require('vue').version = null
将 Vue 的版本号抹去,从而避免了这个问题。
然后,我们编写读取工程中 svg 文件的源码的函数。通常设计师给到我们的 svg 文件会包含很多没用的信息,比如编辑器元数据、注释、隐藏元素、默认值等,这些信息可以删去,而不影响 SVG 呈现结果的内容,所以我们得到工程中 svg 文件的源码后,还要对其进行一些清理,从而保证 svg 代码的干净。
读取工程中 svg 文件的源码,并对 svg 源码进行清理的函数实现如下:
function createSvgSanitizer(src) {
this.removeAttr = (...attrs) => {
src = removeAttr(src, ...attrs)
return this
}
this.removeSvgAttr = (...attrs) => {
src = removeSvgAttr(src, ...attrs)
return this
}
this.removeComment = () => {
src = removeComment(src)
return this
}
this.removeUselessTags = () => {
src = removeUselessTags(src)
return this
}
this.refill = () => {
src = refill(src)
return this
}
this.svg = () => src
return this
}
function readSvgs() {
const svgFiles = readSvgDirectory(ICONS_DIR)
return svgFiles.map(svgFile => {
const name = path.basename(svgFile, '.svg')
const normalizedName = normalizeName(name)
const contents = readSvg(svgFile, ICONS_DIR).trim()
const svgSanitizer = createSvgSanitizer(contents)
svgSanitizer
.removeComment()
.removeUselessTags()
.removeAttr('id')
.removeSvgAttr('width', 'height')
.refill()
const svg = svgSanitizer.svg()
return {
name: normalizedName,
svg
}
})
}
为了阅读的体验,这里就不贴完整的代码实现了,因为太长了,完整的代码可在后面提供的源码中查看。
获取到工程中干净的 svg 源码后,将每个 svg 文件的源码添加到 Vue 的 Template 模板中,从而将 svg 文件构造成 Vue 组件,然后放入临时文件目录中。然后借助 v2s
的能力将 Vue 组件转换成 ts 文件,就可以使用 TypeScript 提供的 tsc
命令将相关的 ts 文件都编译成 js ,最后将临时文件删掉,就完成了整个 svg 图标库的构建流程。构建的入口函数在 generateVue3
中
async function generateVue3(icons, basePath) {
// 省略一些非核心代码
for (const { name, svg } of icons) {
await fse.writeFile(
path.resolve(tempPath, `${name}.vue`),
'<template>\n' +
svg +
'\n' +
'</template>\n' +
'<script lang="ts">\n' +
`import { defineComponent } from 'vue'\n` +
'export default defineComponent({\n' +
` name: '${name}'\n` +
'})\n' +
'</script>'
)
}
await generateIndex(names, '.ts', '.vue', tempPath)
await generateAsyncIndex(names, '.ts', '.vue', tempPath)
const dir = await fse.readdir(tempPath)
const paths = dir.map((fileName) => path.resolve(tempPath, fileName))
await v2s(paths, {
deleteSource: true,
refactorVueImport: true
})
const compilerOptionsBase = {
forceConsistentCasingInFileNames: true,
moduleResolution: 'node',
target: 'ES6',
lib: ['ESNext', 'DOM'],
types: [], // ignore @types/react, which causes error
declaration: true
}
console.log(' tsc to vue3 (cjs)')
await tsc(
{
include: ['_vue3/**/*'],
compilerOptions: {
...compilerOptionsBase,
outDir: 'vue3/lib',
module: 'CommonJS'
}
},
basePath
)
console.log(' copy cjs output to root')
const cjsDir = await fse.readdir(path.resolve(basePath, 'vue3/lib'))
for (const file of cjsDir) {
await fse.copy(
path.resolve(basePath, 'vue3/lib', file),
path.resolve(basePath, 'vue3', file)
)
}
console.log(' tsc to vue3 (esm)')
await tsc(
{
include: ['_vue3/**/*'],
compilerOptions: {
...compilerOptionsBase,
outDir: 'vue3/es',
module: 'ESNext'
}
},
basePath
)
// remove _vue3
console.log(' remove _vue3')
await fse.remove(tempPath)
}
上面的 generateVue3
函数也不是完整的实现,这里主要讲述 svg 图标库的构建流程,完整的实现可在后面提供的源码中查看。
构建流程总结
整个流程还是比较简单的。
最后
以上就是本篇文章的全部内容,工程的源码可以在这里 zentvicons 查看。“纸上得来终觉浅,绝知此事要躬行”,大家赶紧自己尝试构建一个自己的图标库吧 ~
转载自:https://juejin.cn/post/7256161389022199866