给 Vite 写个插件 —— SVG 优化
Sep 01, 2022
我最近在开发一个 vue3
+ tsx
+ vite
项目的时候,发现有个用户体验上的问题:
我在切换页面的时候,如果下一个页面有静态资源,比如 SVG
,那么当网速慢的情况下,切换完页面后会有短暂的空白,如果有动画,就会让人感觉 动画有点卡顿
,即使我提前固定了高度
效果如下:
那么该怎么解决这个问题?
一点想法
我一开始想到的方法是:当用户在第一页的时候,就提前加载第二页的 SVG
不过这有个问题:那我是不是在第一页的时候要请求第二页的 SVG
,在第二页的时候也要请求第三页的 SVG
,以此类推?
我想一劳永逸地的解决这个问题:我把整个应用的所有 SVG
一次性加载,行不行?
这个时候我想到一个技术,叫做 —— CSS 雪碧图
让我想起之前在猪场游戏开发时经常做的事 —— 「打图集」
但是!CSS 雪碧图
这个东西,已经「过时了」—— 并且这个方式不太适合 矢量图
,它适合 .png
,而我用的是 .svg
那能不能我将所有 SVG
都打包成一个大的 SVG
,然后用到的时候就显示,不用到的时候就把宽高设置为 0,并且隐藏?
那就得使用 SVG Sprites
!
不过,webpack
有很多插件在 Vite
上没有,比如我目前想要的这个插件,所以就写一个 SVG Sprites
吧!
实现步骤
一、思路
- 如果加载的是
svg
,就遍历某个文件目录 - 将遍历的这些内容放进一个大东西里
- 这个大东西插到
div
里 - 再把这个
div
插到body
里
二、安装 svgo
与 svgstore
1 | pnpm i -D svgo svgstore |
三、开始写插件
1. 设计
新建这么个文件,这个文件就是我给 Vite
写的一个插件
插件里有两个重要的函数:resolveId
和 load
resolveId
用于解析/兼容
load
就相当于webpack
的loader
,下面会有实现思路
3. 为什么要写 resolveId
?
这个很简单:只是为了兼容编辑器,免得在其他文件引用的时候
@svgstore
的时候会报错
我们包含 所有 SVG
的 大SVG
其实是不存在的,那么就没办法 import,所以我们要在 main.ts
里引入 @svgstore
,但是有一些编辑器不支持这样引入,会报错 xxx不存在
,所以我们要做这么一个 没必要的判断
1 | resolveId(id) { |
4. 写 load
的思路
这其实就是
webpack
的loader
- 如果加载的
文件id
是@svgstore
,我就创建一个sprites
,sprites
是svgstore
提供的一个函数 - 这个
sprites
是一个空的大SVG
,最终想要它包含所有 svg
- 我们遍历了
'src/assets/icons'
目录下的所有文件
,也就是某个目录下的所有SVG
,文件名
对应svg 的 id
,文件内容
对应svg 的 内容
,最后把这些svgid
和code
加到sprites
里 - 到这里其实就已经完成了,不过还需要使用
svgo
来做优化 svg 文件:删除一些没有用的属性、空格…,这样可以让文件变得更小 - 最后我们把
code
变成一个js
文件return
出去 —— 因为我们最终是要生成svg_bundle.js
的js文件
,所以这个内容必须是一个合法的 JavaScript
- 我们创建一个
div
,div
的内容就是大SVG
的内容 没用到的 svg
我们就把它的宽高设置为 0,并且隐藏起来,如果用到了再从后面插入
5. 最终代码
1 | import path from 'path' |
四、配置/引用插件
1. tsconfig.node.json
在
"include"
里把"src/vite_plugins/**/*"
加进去
1 | { |
2. vite.config.ts
引入
svgstore
,并且使用svgstore()
1 | import { defineConfig } from 'vite' |
五、使用插件
1. 在 main.ts
引入
1 | // ... some codes |
2. 使用 svg
语法是:
1
2
3 <svg>
<use xlinkHref='#<文件名>' >
</svg>
1 | export const Demo = () => ( |
最终效果
是不是很爽滑?
cd ../