From a3269f9d820db3c6ebd947221927615bea30fcc4 Mon Sep 17 00:00:00 2001 From: Yufan You Date: Tue, 24 Jan 2023 02:31:13 +0800 Subject: [PATCH] feat: add the inferDimensions option (#18) * feat: add the dimensions option The dimensions option adds `width` and `height` attributes to the element based on the dimensions of the original image. * fix: use dimensions of the last src instead of the input * fix: fix dimensions option in production build * fix: use inferDimensions instead of dimensions as the option name --- src/api.ts | 8 ++++ src/presets.ts | 10 +++-- src/types.ts | 1 + tests/api.spec.ts | 86 +++++++++++++++++++++++++++++++++++++++++ tests/assets/white.png | Bin 0 -> 77 bytes tests/presets.spec.ts | 5 +++ 6 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 tests/api.spec.ts create mode 100644 tests/assets/white.png diff --git a/src/api.ts b/src/api.ts index 291f4db..bd0e106 100644 --- a/src/api.ts +++ b/src/api.ts @@ -97,6 +97,14 @@ export function createImageApi (config: Config) { Object.assign(lastImage, preset.attrs) lastImage.src ||= lastSrc + if (preset.inferDimensions) { + const { args, generate } = last(last(preset.images).srcset) + const lastSharp = await generate(loadImage(resolve(config.root, filename)), args) + const { info: { width, height } } = await lastSharp.toBuffer({ resolveWithObject: true }) + lastImage.width ||= width + lastImage.height ||= height + } + return imagesAttrs }, } diff --git a/src/presets.ts b/src/presets.ts index 945265f..f5bf142 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -11,6 +11,7 @@ interface WidthPresetOptions extends ImageAttrs { resizeOptions?: ResizeOptions withImage?: ImageGenerator media?: string + inferDimensions?: boolean } export { mimeTypeFor } @@ -23,13 +24,14 @@ export function hdPreset (options: WidthPresetOptions) { const highDensity = widthPreset({ density: 2, media: '(-webkit-min-device-pixel-ratio: 1.5)', ...options }) const desktopWidth = Math.max(...options.widths as any) || 'original' const desktop = widthPreset({ ...options, widths: [desktopWidth] }) - return { attrs: desktop.attrs, images: highDensity.images.concat(desktop.images) } + return { attrs: desktop.attrs, images: highDensity.images.concat(desktop.images), inferDimensions: options.inferDimensions } } -export function widthPreset ({ density, widths, formats, resizeOptions, withImage, ...options }: WidthPresetOptions): ImagePreset { +export function widthPreset ({ density, widths, formats, resizeOptions, withImage, inferDimensions, ...options }: WidthPresetOptions): ImagePreset { const [attrs, sourceAttrs] = extractSourceAttrs(options) return { attrs, + inferDimensions, images: Object.entries(formats) .map(([format, formatOptions]) => ({ ...sourceAttrs, @@ -61,16 +63,18 @@ interface DensityPresetOptions extends ImageAttrs { resizeOptions?: ResizeOptions withImage?: ImageGenerator media?: string + inferDimensions?: boolean } function multiply (quantity: number, n?: number | undefined) { return n ? quantity * n : undefined } -export function densityPreset ({ baseWidth, baseHeight, density, formats, resizeOptions, withImage, ...options }: DensityPresetOptions): ImagePreset { +export function densityPreset ({ baseWidth, baseHeight, density, formats, resizeOptions, withImage, inferDimensions, ...options }: DensityPresetOptions): ImagePreset { const [attrs, sourceAttrs] = extractSourceAttrs(options) return { attrs, + inferDimensions, images: Object.entries(formats) .map(([format, formatOptions]) => ({ ...sourceAttrs, diff --git a/src/types.ts b/src/types.ts index e155298..790fe4f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,6 +56,7 @@ export interface ImageSource { export interface ImagePreset { attrs?: ImageAttrs + inferDimensions?: boolean images: ImageSource[] } export type ImagePresets = Record diff --git a/tests/api.spec.ts b/tests/api.spec.ts new file mode 100644 index 0000000..c89fa13 --- /dev/null +++ b/tests/api.spec.ts @@ -0,0 +1,86 @@ +import { describe, expect, test } from 'vitest' +import type { ImagePreset } from 'vite-plugin-image-presets' +import ImagePresetsPlugin, { formatPreset, widthPreset } from 'vite-plugin-image-presets' + +async function getResolvedImage (preset: ImagePreset, isBuild: boolean) { + const plugin = ImagePresetsPlugin({ default: preset }) + await plugin.configResolved({ + base: '', + command: isBuild ? 'build' : 'serve', + root: __dirname, + build: { assetsDir: '' } as any, + } as any) + + return plugin.api.resolveImage('assets/white.png', { preset: 'default' }) +} + +describe('inferDimensions', () => { + test('original width and height', async () => { + const resolved = await getResolvedImage(formatPreset({ + inferDimensions: true, + formats: { + webp: { quality: 70 }, + original: {}, + }, + }), false) + expect(resolved[1]).toHaveProperty('width', 5) + expect(resolved[1]).toHaveProperty('height', 3) + }) + + test('original width and height in production build', async () => { + const resolved = await getResolvedImage(formatPreset({ + inferDimensions: true, + formats: { + webp: { quality: 70 }, + original: {}, + }, + }), true) + expect(resolved[1]).toHaveProperty('width', 5) + expect(resolved[1]).toHaveProperty('height', 3) + }) + + test('scaled width and height', async () => { + const resolved = await getResolvedImage(widthPreset({ + inferDimensions: true, + widths: [4, 2], + formats: { + webp: { quality: 70 }, + }, + }), false) + expect(resolved[0]).toHaveProperty('width', 2) + expect(resolved[0]).toHaveProperty('height', 1) + }) + + test('overriden by attrs', async () => { + const resolved = await getResolvedImage(formatPreset({ + inferDimensions: true, + height: 20, + formats: { + original: {}, + }, + }), false) + expect(resolved[0]).toHaveProperty('width', 5) + expect(resolved[0]).toHaveProperty('height', 20) + }) + + test('inferDimensions = false', async () => { + const resolved = await getResolvedImage(formatPreset({ + inferDimensions: false, + formats: { + original: {}, + }, + }), false) + expect(resolved[0]).not.toHaveProperty('width') + expect(resolved[0]).not.toHaveProperty('height') + }) + + test('inferDimensions undefined', async () => { + const resolved = await getResolvedImage(formatPreset({ + formats: { + original: {}, + }, + }), false) + expect(resolved[0]).not.toHaveProperty('width') + expect(resolved[0]).not.toHaveProperty('height') + }) +}) diff --git a/tests/assets/white.png b/tests/assets/white.png new file mode 100644 index 0000000000000000000000000000000000000000..fde2215f59dc91036cc77ef52e1758164422e6b6 GIT binary patch literal 77 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1n!2~3)gaoYzQX-x%jv*Y^lmGnxZ_g~0kmSJn aqK2W%jG37uu3j7{&*16m=d#Wzp$Pz4JrVu@ literal 0 HcmV?d00001 diff --git a/tests/presets.spec.ts b/tests/presets.spec.ts index 13124a6..0d9c400 100644 --- a/tests/presets.spec.ts +++ b/tests/presets.spec.ts @@ -17,6 +17,7 @@ describe('formatPreset', () => { expect(preset.attrs).toEqual({}) expect(preset.images.length).toEqual(2) + expect(preset.inferDimensions).toBeUndefined() const [avifImage, originalImage] = preset.images @@ -42,10 +43,12 @@ describe('widthPreset', () => { webp: { quality: 70 }, jpg: { quality: 80 }, }, + inferDimensions: true, }) expect(preset.attrs).toEqual({ class: 'img', loading: 'lazy' }) expect(preset.images.length).toEqual(2) + expect(preset.inferDimensions).toEqual(true) const [webpImage, jpegImage] = preset.images @@ -75,10 +78,12 @@ describe('densityPreset', () => { webp: { quality: 70 }, avif: { quality: 80 }, }, + inferDimensions: false, }) expect(preset.attrs).toEqual({ loading: 'lazy' }) expect(preset.images.length).toEqual(2) + expect(preset.inferDimensions).toEqual(false) const [webpImage, avifImage] = preset.images