前言
去年写过一篇关于 vite-plugin-image-tools 的「从零到一」小作文,讲的是怎么用 sharp 把图片压一压、转一转,顺便在开发和生产环境都折腾一下,这里特别感谢大家的喜欢收藏。
没想到写着写着,插件就 4.0 了,功能也越堆越多——再不说两句,怕是连自己都记不住改了什么😂。所以这篇就专门聊聊 4.0 的新玩意儿,顺便把实现思路捋一捋,方便以后翻车时排查(不是)。
仓库
github: github.com/illusionGD/…
现在4.0.0版本刚发布,基本重构了一遍,可能会多多少少有点bug,请见谅,欢迎提iusse (~ ̄▽ ̄)~
新增功能概览
4.0 在「压缩 + WebP + 精灵图」的老本行上,又塞了一堆新能力,大致长这样:
| 功能 | 说明 |
|---|---|
| convert | 接替 enableWebp 的「格式转换大总管」,webp、avif 随便选 |
| perImage | 单图 VIP 通道,某张图想特殊待遇?安排 |
| enableDevConvert | 开发环境也能转格式了,默认 webp,不用每次都 build 才能看效果 |
| 精灵图 name | 输出文件名终于能自定义了,不想要 xxx-sprites.png?自己起 |
| 精灵图 dev watcher | 改一张图就自动重建精灵图 + 刷新,告别手动重启 |
| cssGen | 扫一遍图片目录,自动生成 CSS 类,icon_hover.png 直接变 :hover |
| cssGen dev watcher | 同上,改图就重新生成 CSS,懒人福音 |
补充说明:上一篇没讲精灵图,这里补一句——精灵图就是把目录下多张图合并成一张雪碧图,插件会自动修改 CSS 中的 background-image、background-position、background-size 等,减少 HTTP 请求。4.0 在精灵图的基础上增加了 dev watcher——也就是开发监听图片资源更改并及时更新。
快速配置
极简版(和以前一样,三行搞定):
VitePluginImageTools({
quality: 90, // 压缩质量 0–100
enableDev: true, // 开发环境启用图片处理
enableDevConvert: true // 开发环境也转格式(默认 webp)
})
详细版(能开的都开了):
VitePluginImageTools({
quality: 90,
enableDev: true,
enableDevConvert: true,
// 格式转换:替代原 enableWebp,支持 webp、avif 等
convert: {
enable: true,
format: 'webp',
limitSize: 2 * 1024 // 小于此体积才转,单位 KB
},
// 单图级配置,可按路径覆盖质量、格式
perImage: async (filePath) => {
if (filePath.includes('hero.jpg')) return { format: 'avif', quality: 60 }
return {}
},
// 精灵图:合并目录内图片为雪碧图
spritesConfig: {
rules: [
{ dir: './src/assets/icons', name: 'icons' } // name 可选,默认 dir名-sprites
]
},
// 根据图片目录自动生成 CSS 类
cssGen: {
rules: [{
inputDir: './src/assets/icons',
stylePath: 'assets/generated/image-classes.css',
classPrefix: 'ui--',
variantRules: [{ regex: /_hover$/, pseudo: ':hover' }] // icon_hover.png → :hover
}]
}
})
实现原理
1. convert 与 perImage:格式转换的「总开关」和「单独开关」
convert 把原来的 enableWebp 升级成了「格式转换控制中心」,想转啥格式、要不要删原图,都在这儿说了算。perImage 则是在转换前给单张图开个后门:比如 hero 图必须 avif,缩略图质量砍半,都可以按路径单独安排。
[图片] → 过滤 → perImage 有特殊要求?→ convert 统一转换 → 输出
思路:遍历打包产物中的图片时,先按路径问 perImage 有没有覆盖配置,没有就用 convert 的默认规则,最后走统一的转换和路径替换流程。
const single = await perImage(join(cwd(), sourcePath))
const targetFormat = single?.format || convert.format || 'webp'
2. 精灵图:生成与 CSS 替换
精灵图分两步:
- 合并图片并拿到坐标
- 改 CSS 里的
background-*。
① 生成精灵图:按规则读取目录,过滤出图片文件,用精灵图库按排布算法(如 binary-tree)拼成一张 PNG,同时拿到每张原图在合成图里的 x、y、宽高,存起来供后续替换用。
[规则目录] → 过滤图片 → 排布算法合并 → 输出 PNG + 每张图的坐标信息
const Spritesmith = (await import('spritesmith')).default
const result = await new Promise((resolve, reject) => {
Spritesmith.run(
{ src: files, algorithm, padding: rule?.padding || 0 },
(err, result) => (err ? reject(err) : resolve(result))
)
})
originalStyles[dir] = { ...result, outPathName }
Object.keys(result.coordinates).forEach((p) => spriteImageIndex.set(p, dir))
② 替换 CSS:在 CSS 处理阶段,解析出 url(...) 里的图片路径,转成绝对路径后判断是否属于某精灵图目录;若是,则把 background-image 换成精灵图路径,并根据坐标补上 background-position、background-size、background-repeat(负偏移 + 精灵图总尺寸)。支持 transformUnit、rootValue 做 px/rem 转换。
生产环境打包后路径会变,所以需要在产物阶段再对 CSS 做一次同样的替换。
// 匹配 url(...),解析为绝对路径,查是否属于精灵图
const filePath = resolveImagePath(url, id)
const targetSprite = filterSpriteImg(filePath)
if (!targetSprite) return
decl.value = value.replace(url, spritePath)
modifySpritesCss(rule, targetSprite, filePath)
// 补全的样式
rule.append(
{ prop: 'background-position', value: `-${x}px -${y}px !important` },
{ prop: 'background-size', value: `${spriteWidth}px ${spriteHeight}px !important` },
{ prop: 'background-repeat', value: 'no-repeat !important' }
)
使用注意:
- 引用精灵图的 CSS 规则里,
width、height需填写具体数值(px 或 rem),不能是%、vw、vh等,否则会按原图尺寸计算,导致background-size、background-position不准。 - 不能把宽高和背景图分开两个class写,因为编译时很难根据图片的class去查询其他的class
// bad
.class-1 {
width: 100px;
height: 100px;
}
.class-2 {
background-image: url(...);
}
// good
.class-name{
width: 100px;
height: 100px;
background-image: url(...);
}
- 用 rem 时需配置
rootValue做 px 转换。
3. 精灵图 dev watcher:改图即生效,不用再「重启大法」
以前改个 icon 得重启 dev server 才能看到新精灵图。现在不用了。思路:在开发服务器启动时,对精灵图源目录加监听,源图增删改时防抖重建精灵图,清掉模块缓存并触发整页刷新。
[源图变化] → 监听发现 → 防抖 → 重建精灵图 → 清缓存 + 刷新
有个小坑:插件自己写出的精灵图 PNG 也会触发 change 事件,需要忽略自身输出,否则会陷入「我写 → 触发 → 我再写」的死循环😅。
if (spriteGeneratedOutputs.has(absFile)) return // 自己写的,别理
4. cssGen:扫图片目录生成 CSS
好处:
- 提效——不用手写一堆 background 和尺寸,只需要拼出选择器和样式就行;
- 方便移动图片资源——增删改图片只需动文件,CSS 自动更新,不用到处改引用路径。
_hover自动变:hover:可以自定义css的伪类和图片名称的映射关系,自动生成伪类样式,如hover
思路:递归扫描配置的图片目录,按文件名生成对应的 CSS 类。variantRules 负责把文件名里的变体(如 _hover)映射成伪类(如 :hover),例如 icon_hover.png 会生成 .ui--icon 和 .ui--icon:hover 两条规则,分别写上 background-image、宽高。用图片库读尺寸,拼出选择器和样式,写入指定 CSS 文件。
[扫目录] → 匹配变体规则 → 拼选择器 + 样式 → 写 CSS 文件
// icon_hover.png → baseName: icon, pseudo: :hover
const variant = resolveVariant(rule, parsed.name)
// 输出 .ui--icon { ... } 和 .ui--icon:hover { ... }
5. cssGen dev watcher:改图即重新生成 CSS
和精灵图 watcher 一个套路:监听 cssGen 的输入目录,有变化就重新生成 CSS,若有变更则失效缓存并刷新。从此改图不用惦记「我有没有重新 build 一下」了。
cssGenWatchDirs.forEach((dir) => server.watcher.add(dir))
// on change → 重新生成 CSS → 有变更则 invalidateAll + full-reload
总结
- 4.0 主打一个「能自动就自动」:convert/perImage 管格式,精灵图 + cssGen 管产出,两套 dev watcher 管热更新,省心就完事了
- 实现上就是 Vite 那套钩子 +
server.watcher的组合拳,没啥黑魔法 - 仓库:vite-plugin-image-tools,有问题欢迎 issue,有想法欢迎 PR~