用Vite和Sharp Library优化图像
我想讨论一种利用尖锐库来创建简单的Vite插件的方法。该插件的目的是:
- 压缩原始图像。
- 以
.webp
和.avif
格式生成轻量级版本。 - 在开发过程中提供最少的手动工作。
tl; dr :
- github存储库:Link
为什么选择这个?
- 像
.webp
和.avif
这样的现代图像格式在保持透明度的同时,大小可显着降低。 - 通常,提供项目中的图像没有压缩,导致不必要的膨胀。
- 使用诸如TinyPNG之类的工具的手动图像压缩是乏味的。自动化此过程是前进的道路。
使用sharp
库将与此努力保持关键。
- 尖锐的文档:Link
概念:
当我们将图像导入import Image from './images/hero.png?optimized';
时,插件不仅应返回图像链接。相反,它应返回包含多个链接的对象。然后可以将此对象馈入<picture>
HTML标签以进行相应的渲染。
这是一个苗条的组件(Picture.svelte
)来证明这一点:
<script lang="ts">
type OptimizedSrc = {
avif?: string;
webp?: string;
fallback: string;
};
export let src: OptimizedSrc | string;
function getSource(src: OptimizedSrc | string): OptimizedSrc {
if (typeof src === 'string') return { fallback: src };
return src;
}
$: sources = getSource(src);
</script>
<picture>
{#if sources.avif}
<source srcset={sources.avif} type="image/avif" />
{/if}
{#if sources.webp}
<source srcset={sources.webp} type="image/webp" />
{/if}
<img src={sources.fallback} alt="" />
</picture>
用法:
<script lang="ts">
import Image from '../images/source.png?optimized';
import Picture from './Picture.svelte';
</script>
<div>
<Picture src={Image} />
</div>
制作插件:
我建议参考Vite和Follup提供的详尽文档:
- Vite Plugin Guide
- Rollup Plugin Development
-
Scaffolding with Vite:
npm create vite@latest my-dir --template svelte-ts
设置应用程序后,请确保您在Vite配置中注册该插件。它应该先优先考虑。
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { imageOptimizerPlugin } from './imageOptimizerPlugin';
export default defineConfig({
plugins: [imageOptimizerPlugin(), sveltekit()]
});
实际上,插件本身的整个代码。
// imageOptimizerPlugin.ts
import path, { basename, extname } from 'node:path';
import { Plugin } from 'vite';
import sharp from 'sharp';
const pattern = '?optimized';
const isProd = process.env.NODE_ENV === 'production';
function isIdForOptimization(id: string | undefined) {
return id?.includes(pattern);
}
const forSharpPattern = /\?(webp|avif|fallback)/;
function isIdForSharp(id: string | undefined) {
return forSharpPattern.test(id || '');
}
function resolveId(id: string, importer: string) {
return path.resolve(path.dirname(importer), id);
}
export const imageOptimizerPlugin = (): Plugin[] => {
return [
{
name: '?sharp-handler',
enforce: 'pre',
async resolveId(id, importer) {
if (!isIdForSharp(id)) return;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return resolveId(id, importer!);
},
async load(id) {
if (!isIdForSharp(id)) return;
const unwrappedId = id.replace(forSharpPattern, '');
let [, extension] = id.split('?');
let buffer: Uint8Array;
if (extension === 'fallback') {
buffer = await sharp(unwrappedId)
.png({ quality: 70, effort: 7, compressionLevel: 6 })
.toBuffer();
} else if (extension === 'webp') {
buffer = await sharp(unwrappedId).webp({ quality: 80 }).toBuffer();
} else {
buffer = await sharp(unwrappedId).avif({ quality: 60 }).toBuffer();
}
if (extension === 'fallback') extension = extname(unwrappedId).replace('.', '');
const name = basename(unwrappedId, extname(unwrappedId)) + `.${extension}`;
const referenceId = this.emitFile({
type: 'asset',
name: name,
needsCodeReference: true,
source: buffer
});
return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
}
},
{
name: '?optimized-handler',
enforce: 'pre',
async resolveId(id, importer) {
if (!isIdForOptimization(id)) return;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return resolveId(id, importer!);
},
async load(id) {
if (!isIdForOptimization(id)) return;
const unwrappedId = id.replace(pattern, '');
if (!isProd) {
return {
code: `import fallback from "${unwrappedId}";` + `export default { fallback };`,
map: null
};
}
const webp = JSON.stringify(unwrappedId + '?webp');
const avif = JSON.stringify(unwrappedId + '?avif');
const fallback = JSON.stringify(unwrappedId + '?fallback');
return (
`import webp from ${webp};` +
`import avif from ${avif};` +
`import fallback from ${fallback};` +
`export default {webp, avif, fallback};`
);
}
}
];
};
让我们讨论它的工作原理
您的主要Vite插件实际上由两个子插曲组成:
-
尖锐的处理程序(
?sharp-handler
):- 职责:处理实际图像格式。
- 解决逻辑:解决图像ID时,它会滤除不需要通过夏普处理的图像。
-
加载逻辑:基于要生成的特定图像格式,调整了尖锐的参数。每种图像格式(
.webp
,.avif
或压缩原始)都是使用其各自的设置生成的。然后将处理后的图像缓冲区发射为具有参考ID的资产。
-
优化的处理程序(
?optimized-handler
):- 职责:处理自定义图像导入。
- 分辨率逻辑:仅针对以优化标记的图像。
- 加载逻辑:在开发模式下,它返回原始图像以更快的性能。在生产模式下,它处理图像以生成优化版本并使用所有可用格式构造导出语句。
开发与产品模式:
该插件的行为不同:
-
在开发模式中
在生产模式中,图像已完全处理,同时生成
.webp
和.avif
格式以及压缩原始。
最后,为防止由于我们的自定义导入而导致的绒毛错误,请从我们量身定制的模块中通知有关预期返回格式的打字稿:
// optimized-module.d.ts
declare module '*?optimized' {
const value: {
webp?: string;
avif?: string;
fallback: string;
};
export default value;
}
总而言之,使用此插件,您的图像将自动优化和转换。例如,在我的测试回购中,有效地将3.1 MB图像降低到746 kb,而.webp
和.avif
版本的重量分别仅为92 kb和66 kb。
感谢您的阅读!我相信您会找到这种方法。 ð