前言
在前端开发中,图片优化一直是一个令人头疼的问题。无论是压缩图片大小,还是将图片格式转换为更高效的 WebP 格式,手动操作不仅繁琐,还容易出错。最近,我在使用 Vite 打包工具时,发现了一个非常实用的插件,可以自动完成图片压缩和格式转换,彻底解放双手。

需求分析
在开发中,我们通常希望实现以下功能:
-
- 自动压缩图片:能够根据配置调整图片质量,减少文件大小。
-
- 自动转 WebP 格式:将图片转换为 WebP 格式,提升加载速度。
-
- 路径替换 :将图片引用路径的后缀自动替换为
.webp
。
- 路径替换 :将图片引用路径的后缀自动替换为
-
- 支持开发和生产环境:在开发环境中实时预览优化效果,在生产环境中高效打包。
技术实现
为了实现这些功能,我选择了一个基于 Vite 的插件,它使用了 sharp
库来处理图片压缩和格式转换。以下是具体实现思路。
生产环境优化
在生产环境中,我们主要通过 Vite 的 generateBundle
钩子函数来处理图片。
-
- 图片压缩 在打包过程中,插件会遍历所有图片资源,使用
sharp
对图片进行压缩。以下是核心代码:
javascriptexport default function ImageOptimizer() { return { async generateBundle(options, bundle) { for (const key in bundle) { const chunk = bundle[key]; const { ext } = path.parse(key); if (!/(png|jpg|jpeg|webp)$/.test(ext)) { continue; } if (chunk.source instanceof Buffer) { const compressedBuffer = await compressImage(chunk.source); chunk.source = compressedBuffer; } } } }; } async function compressImage(buffer) { return sharp(buffer) .resize(800) .toFormat("webp", { quality: 80 }) .toBuffer(); }
这段代码会将图片压缩为 WebP 格式,并设置质量为 80,同时限制最大宽度为 800 像素。
- 图片压缩 在打包过程中,插件会遍历所有图片资源,使用
-
- 路径替换 为了将图片路径的后缀替换为
.webp
,我们需要在打包后的 CSS 和 JS 文件中进行替换。以下是实现逻辑:
javascriptfunction replaceWebpPaths(str, map) { let result = str; for (const key in map) { result = result.replace(new RegExp(key, "g"), map[key]); } return result; } export default function ImageOptimizer() { return { async generateBundle(options, bundle) { const pathMap = {}; for (const key in bundle) { const chunk = bundle[key]; const { ext } = path.parse(key); if (/(png|jpg|jpeg)$/.test(ext)) { const webpName = key.replace(ext, ".webp"); pathMap[key] = webpName; } if (/(js|css)$/.test(key)) { if (/(js)$/.test(key)) { chunk.code = replaceWebpPaths(chunk.code, pathMap); } else if (/(css)$/.test(key)) { chunk.source = replaceWebpPaths(chunk.source, pathMap); } } } } }; }
这段代码会收集所有图片的原始路径和 WebP 路径的映射关系,并在 CSS 和 JS 文件中替换路径。
- 路径替换 为了将图片路径的后缀替换为
开发环境优化
在开发环境中,我们希望实时看到优化效果,同时避免每次修改都重新处理图片。为此,插件在 Vite 的 configureServer
钩子中加入了缓存机制。
-
- 实时优化在开发服务器中,插件会拦截图片请求,动态处理图片并返回优化后的结果:
javascriptexport default function ImageOptimizer() { return { configureServer(server) { server.middlewares.use(async (req, res, next) => { const url = req.url || ""; if (!url.endsWith((".png", ".jpg", ".jpeg"))) { return next(); } try { const filePath = path.resolve( process.cwd(), url.split("?")[0].slice(1) ); const buffer = fs.readFileSync(filePath); const optimizedBuffer = await compressImage(buffer); res.setHeader("Content-Type", "image/webp"); res.end(optimizedBuffer); } catch (e) { next(); } }); } }; }
-
- 缓存机制为了避免重复处理相同的图片,插件会为每张图片生成一个唯一的缓存键,并将优化后的图片存储在缓存目录中:
javascriptfunction generateCacheKey(filePath, options) { const hash = crypto .createHash("md5") .update(fs.readFileSync(filePath)) .update(JSON.stringify(options)) .digest("hex"); const { name, ext } = path.parse(filePath); return `${name}_${hash.slice(0, 8)}${ext}`; } export default function ImageOptimizer() { return { configureServer(server) { server.middlewares.use(async (req, res, next) => { const url = req.url || ""; if (!url.endsWith((".png", ".jpg", ".jpeg"))) { return next(); } const filePath = path.resolve( process.cwd(), url.split("?")[0].slice(1) ); const cacheKey = generateCacheKey(filePath, { quality: 80 }); const cachePath = path.join( "node_modules/.cache", cacheKey ); if (fs.existsSync(cachePath)) { res.end(fs.readFileSync(cachePath)); return; } const buffer = fs.readFileSync(filePath); const optimizedBuffer = await compressImage(buffer); fs.writeFileSync(cachePath, optimizedBuffer); res.setHeader("Content-Type", "image/webp"); res.end(optimizedBuffer); }); } }; }
总结
通过这个插件,我们可以在开发和生产环境中轻松实现图片的自动化优化。它不仅支持图片压缩和格式转换,还能自动替换路径后缀,极大提升了开发效率。如果你对这个插件感兴趣,可以尝试集成到自己的项目中,体验一把"解放双手"的快感!