Skip to content

Commit

Permalink
feat: support 'original' format and widths in presets
Browse files Browse the repository at this point in the history
  • Loading branch information
ElMassimo committed Jan 12, 2022
1 parent ad48252 commit 822ffb1
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
55 changes: 38 additions & 17 deletions src/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResizeOptions, 'width'> {
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<WidthPresetOptions, 'widths'>) {
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
},
})),
})),
}
Expand All @@ -30,30 +43,38 @@ 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)
.map(([format, formatOptions]) => ({
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
},
})),
})),
}
Expand Down
12 changes: 8 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<ImageFormat> {
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')
Expand All @@ -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}`
}
Expand Down

0 comments on commit 822ffb1

Please sign in to comment.