From 822ffb1a8ae30b8a9f072c21119e6d6c3d807bdf Mon Sep 17 00:00:00 2001 From: Maximo Mussini Date: Tue, 11 Jan 2022 23:06:38 -0300 Subject: [PATCH] feat: support 'original' format and widths in presets --- src/api.ts | 2 +- src/index.ts | 2 +- src/presets.ts | 55 ++++++++++++++++++++++++++++++++++---------------- src/utils.ts | 12 +++++++---- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/api.ts b/src/api.ts index 79d3f13..3578e82 100644 --- a/src/api.ts +++ b/src/api.ts @@ -72,7 +72,7 @@ export function createImageApi (config: Config) { async function queueImageAndGetFilename (id: string, sourceFilename: string, image: Image) { const base = basename(sourceFilename, extname(sourceFilename)) const hash = getAssetHash(id + await getImageHash(sourceFilename)) - const format = formatFor(image) + const format = await formatFor(image) const filename = `${base}.${hash}.${format}` generatedImages.push(writeImageFile(filename, image)) diff --git a/src/index.ts b/src/index.ts index 4220a7e..8875ac2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,7 +56,7 @@ export default function ImagePresetsPlugin (presets?: ImagePresets, options?: Op if (!image) throw new Error(`vite-image-presets cannot find image with id "${id}" this is likely an internal error`) - res.setHeader('Content-Type', `image/${formatFor(image)}`) + res.setHeader('Content-Type', `image/${await formatFor(image)}`) res.setHeader('Cache-Control', 'max-age=360000') return image.clone() .on('error', err => console.error(err)) diff --git a/src/presets.ts b/src/presets.ts index 18b529d..c4e3c56 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -2,25 +2,38 @@ import type { ResizeOptions } from 'sharp' import type { ImageAttrs, ImageFormat, ImageFormats, ImagePreset } from './types' import { mimeTypeFor } from './utils' -interface WidthPresetOptions extends ImageAttrs, Omit { - widths: number[] - formats: ImageFormats +type FormatOptions = ImageFormats & { original?: {} } + +interface WidthPresetOptions extends ImageAttrs { + widths: (number | 'original')[] + formats: FormatOptions + resizeOptions?: ResizeOptions } export { mimeTypeFor } -export function widthPreset ({ widths, formats, ...attrs }: WidthPresetOptions): ImagePreset { +export function formatPreset (options: Omit) { + return widthPreset({ widths: ['original'], ...options }) +} + +export function widthPreset ({ widths, formats, resizeOptions, ...attrs }: WidthPresetOptions): ImagePreset { return { attrs, images: Object.entries(formats) .map(([format, formatOptions]) => ({ type: mimeTypeFor(format as ImageFormat), srcset: widths.map(width => ({ - condition: `${width}w`, - args: { format, width, formatOptions }, - generate: (image, { format, formatOptions, width }) => - image.toFormat(format, formatOptions) - .resize({ width, withoutEnlargement: true, ...attrs }), + condition: width === 'original' ? undefined : `${width}w`, + args: { format, width, formatOptions, resizeOptions }, + generate: (image) => { + if (format !== 'original') + image = image.toFormat(format as ImageFormat, formatOptions) + + if (width !== 'original') + image = image.resize({ width, withoutEnlargement: true, ...resizeOptions }) + + return image + }, })), })), } @@ -30,14 +43,15 @@ interface DensityPresetOptions extends ImageAttrs { density: number[] baseHeight?: number baseWidth?: number - formats: ImageFormats + formats: FormatOptions + resizeOptions?: ResizeOptions } function multiply (quantity: number, n?: number | undefined) { return n ? quantity * n : undefined } -export function densityPreset ({ baseWidth, baseHeight, density, formats, ...attrs }: DensityPresetOptions): ImagePreset { +export function densityPreset ({ baseWidth, baseHeight, density, formats, resizeOptions, ...attrs }: DensityPresetOptions): ImagePreset { return { attrs, images: Object.entries(formats) @@ -45,15 +59,22 @@ export function densityPreset ({ baseWidth, baseHeight, density, formats, ...att type: mimeTypeFor(format as ImageFormat), srcset: density.map(density => ({ condition: `${density}x`, - args: { format, density, baseWidth, baseHeight, formatOptions }, - generate: (image, { format, formatOptions, baseWidth, baseHeight, width }) => - image.toFormat(format, formatOptions) - .resize({ + args: { format, density, baseWidth, baseHeight, formatOptions, resizeOptions }, + generate: (image) => { + if (format !== 'original') + image = image.toFormat(format as ImageFormat, formatOptions) + + if (baseWidth || baseHeight) { + image = image.resize({ width: multiply(density, baseWidth), height: multiply(density, baseHeight), withoutEnlargement: true, - ...attrs, - }), + ...resizeOptions, + }) + } + + return image + }, })), })), } diff --git a/src/utils.ts b/src/utils.ts index 4da1f0a..5020043 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,7 +13,7 @@ function parseURL (rawURL: string) { export function generateImageID (url: string, args: any) { return createHash('sha256').update(url).update(JSON.stringify(args)).digest('hex').slice(0, 8) - + (args.format ? `.${args.format}` : '') + + (args.format && args.format !== 'original' ? `.${args.format}` : '') } export function getAssetHash (content: string | Buffer) { @@ -24,8 +24,11 @@ export async function exists (path: string) { return await fs.access(path, fsConstants.F_OK).then(() => true, () => false) } -export function formatFor (image: Image): ImageFormat { - const format = (image as any).options?.formatOut +export async function formatFor (image: Image): Promise { + let format = (image as any).options?.formatOut + if (format === 'input') { + format = (await image.metadata()).format + } if (!format) { console.error('Could not infer image format for', image) throw new Error('Could not infer image format') @@ -34,7 +37,8 @@ export function formatFor (image: Image): ImageFormat { return format } -export function mimeTypeFor (format: ImageFormat) { +export function mimeTypeFor (format: ImageFormat | 'original') { + if (format === 'original') return undefined if (format === 'jpg') format = 'jpeg' return `image/${format}` }