大家好,我是前端西瓜哥。
诶哟喂,SVG 怎么没内嵌?
最近啊,西瓜哥我用 vite 去给一个项目构建(vite build)一个应用。打包结果是一个 html 和一些加了哈希的资源。
然后打包出来的文件一看,发现居然有好几个 1 Kb 以下的 SVG 文件。
我搜了下源码,这些 SVG 是这样被使用的:
<img src="./image/somIcon.svg">
不对呀,理论上小于 4 Kb 的静态资源,是会转成 base64 编码字符串,嵌入到其他资源中。
较小的资源体积小于 assetsInlineLimit 选项值 则会被内联为 base64 data URL。
build.assetsInlineLimit 默认值为 4096 (4kb)。
我发现使用库模式(打包成 index.es.js,使用该模式需要设置 build.lib 配置)时,是不会出现 SVG 文件的。
如果你指定了 build.lib,那么 build.assetsInlineLimit 将被忽略,无论文件大小或是否为 Git LFS 占位符,资源都会被内联。
所以一开始我以为我的配置设置的有问题,因为库模式没问题了。
我折腾了大半天,检查配置,查文档,assetsInlineLimit 给你加到 999999,安装其他版本的包,给引入的文件末尾加上 ?inline。各种尝试,都没用。
后来我用最新版的 vite 构建了一个新的 Vue 项目。
发现它这个官方给的 demo 打包出来的文件 SVG 都没做内联。
好家伙,我寻思 vite 本身就不支持 SVG 转 base64 编码内嵌。
设计如此,本就不支持 SVG 转 Base64
走,去翻翻 vite 的 issue,然后找到了一个 3 年前的 issue,编号 1204。
这个 issue 标记为 enhancement,即它是一个增强功能,并不是 bug。
此外可以看到有两个 PR 是要解决这个 issue 的。
一个 PR 被关闭了,一个 PR 是打开着。我们去看看。
先看看被关闭的那个,PR 编号是 1716,是 vite 成员提的 PR。
他说他不赞成 SVG 转成 Base64 嵌入到 HTML,SVG 是个文本类的特殊图片格式,不是二进制,没必要再转一层 Base64,导致体积变大。
因为 Base64 需要用 4 个字符表达原来文本的 3 个字节,会增大 33~36% 的体积。
即希望结果是:
<img src='data:image/svg+xml;utf8,<svg ... > ... </svg>'>
而不是:
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxu...c3ZnPg==">
虽然不打算转 Base64,但还是可以转 utf8 格式的 Data URL 的,一样可以实现内嵌的效果,而且体积更小,于是提了这个 PR。
这个 PR 是有问题的,有些情况没处理,比如转义,去掉 svg fragments 等。
然而过了一个月,这个 PR 还是没进展,说明 优先级并不高。
此时一位路过的野生程序员说他来搭把手。
于是这个 PR 关闭了,这位老哥创建了一个新的 PR。
这个 PR 一直就挂在那
加油啊,路过的程序员老哥。你是我们全村人的希望啊。
看看 PR。
两年前的 PR,至今仍是 Open 状态。
哦豁,凉了。
发生甚么事了?我瞅瞅。
看下 PR 的内容。引入了一个 mini-svg-data-uri 第三方包,来做 SVG 转 DataUrl,改了一些判断条件,因为和普通资源直接走转 base64 不同,SVG 是要直接用原来的文本内容的。改了了几个测试用例。
看着不少 review 和讨论,看着应该还行,有几个 vite 成员 approved 了。
最后时需要有人手动测试是否处理好了 SVG Segment 的情况。此时提 PR 的老哥不见了。
SVG Segment 是 SVG 的一个比较特殊的用法,大概是这样(来自 MDN)。
对于一个 SVG,我们可以用 view 标签指定某种情况下特定的 viewBox 视口范围。
<svg viewBox="0 0 300 100" width="300" height="100"
xmlns="http://www.w3.org/2000/svg">
<view id="one" viewBox="0 0 100 100" />
<circle cx="50" cy="50" r="40" fill="red" />
<view id="two" viewBox="100 0 100 100" />
<circle cx="150" cy="50" r="40" fill="green" />
<view id="three" viewBox="200 0 100 100" />
<circle cx="250" cy="50" r="40" fill="blue" />
</svg>
然后在使用的时候通过 icon.svg#<id> 指定 id,来修改 SVG 最终展示的 viewBox。
<!-- 正常视口 -->
<img src="example.svg" alt="three circles" width="300" height="100" />
<!-- 视口切换到绿色圆的位置 -->
<img src="example.svg#three" alt="blue circle" width="100" height="100" />
效果:
回到 PR。
好久,后面有人帮着测试了,发现有问题。然后就,没有然后了,此外还有因为长期没合入出现的合并冲突。
凑合着用吧
啊这。
我 fork 了 vite 项目,把这位老哥的修改应用到最新版本上,然后 build 了一下,并拿去构建我的一个 demo 项目,结果 SVG 成功变成了 Base64。
然后我运行测试相关的命令,各种不对。
因为有些原来转换为正常 url 的,现在会转成 base64,就匹配不上了。我还发现 css url 的逻辑还有点问题,拿到了一个错误的 none 值。
诶,感觉要提 PR 的话,要修正原来的测试用例,并补充一些新的测试用例,还要处理 css 的情况。行吧,以后再看看。
回到我一开始的需求,行,你不给我转 Base64 是吧?我通过 ?raw 直接拿到 SVG 文本内容,给你动态转成 Base64。
import iconSvg from './image/someIcon.svg?raw'; // 这个会拿到 "<svg ...>...</svg>"
const toSVGDataUrl = (str: string) => {
const base64 = btoa(str);
return `data:image/svg+xml;base64,${base64}`;
};
<img :src="toSVGDataUrl(iconSvg)" />
还行(又不是不能用)。
结尾
这次经历,我认识到 一个大型的开源项目的维护者,对单元测试是非常看重的,因为它影响着千千万万的开发者,必须保证在绝大多数情况下能正确运行。
突然想起之前 VSCode 我更新了最新版本,结果运行一段时间就会报错需要重启。
然后就是 vite 维护者 非常注重性能,毕竟是一个很重要的构建工具。SVG 是可以 Base64 的,实现逻辑也很简单,和其他图形走一样的逻辑。
但 SVG 可以直接用原本的文本数据,更小,有优化空间。因为体积可以优化,所以维护者就宁缺毋滥,宁可丢掉这个功能。
要是我,我可能就先图省事,直接支持 Base64 了,然后有机会再优化(通常不了了之)。
然后是优先级,优先级不高,维护者就是不会主动去实现。
此时就需要社区的力量了,如果你很需要某个功能,就要积极提 PR,积极讨论并主动推进,否则可能像这个 PR 一样,半途而废。