feat: add eager image processing functions (generateAllSizes, convertOriginalToWebp)

This commit is contained in:
Kirill
2026-05-15 19:48:15 +05:00
parent 8b79c01985
commit 5de2694a14
2 changed files with 115 additions and 0 deletions
@@ -97,3 +97,65 @@ describe('image-resize', () => {
await fs.promises.unlink(first.path) await fs.promises.unlink(first.path)
}) })
}) })
describe('eager image processing', () => {
it('generateAllSizes creates all width+format combinations', async () => {
const { generateAllSizes } = await import('../image-resize.js')
const sharp = (await import('sharp')).default
const uuid = 'test-eager-uuid-1'
const testImagePath = path.join(UPLOADS_DIR, `${uuid}.png`)
await sharp({ create: { width: 2000, height: 1500, channels: 3, background: { r: 255, g: 0, b: 0 } } })
.png()
.toFile(testImagePath)
await generateAllSizes(uuid, '', testImagePath)
const cacheDir = path.join(UPLOADS_DIR, '.cache')
for (const width of [320, 640, 1024, 1600]) {
for (const format of ['avif', 'webp']) {
const cachePath = path.join(cacheDir, `${uuid}_w${width}.${format}`)
const exists = await fs.promises.access(cachePath).then(() => true).catch(() => false)
expect(exists).toBe(true)
}
}
// Cleanup
await fs.promises.unlink(testImagePath)
for (const width of [320, 640, 1024, 1600]) {
for (const format of ['avif', 'webp']) {
const cachePath = path.join(cacheDir, `${uuid}_w${width}.${format}`)
try {
await fs.promises.unlink(cachePath)
} catch {
// ignore
}
}
}
})
it('convertOriginalToWebp converts and deletes original', async () => {
const { convertOriginalToWebp } = await import('../image-resize.js')
const sharp = (await import('sharp')).default
const uuid = 'test-eager-uuid-2'
const testImagePath = path.join(UPLOADS_DIR, `${uuid}.png`)
await sharp({ create: { width: 800, height: 600, channels: 3, background: { r: 0, g: 255, b: 0 } } })
.png()
.toFile(testImagePath)
const result = await convertOriginalToWebp(uuid, '')
expect(result).toBe(`/uploads/${uuid}.webp`)
const pngExists = await fs.promises.access(testImagePath).then(() => true).catch(() => false)
expect(pngExists).toBe(false)
const webpPath = path.join(UPLOADS_DIR, `${uuid}.webp`)
const webpExists = await fs.promises.access(webpPath).then(() => true).catch(() => false)
expect(webpExists).toBe(true)
// Cleanup
try {
await fs.promises.unlink(webpPath)
} catch {
// ignore
}
})
})
+53
View File
@@ -64,3 +64,56 @@ export async function getOrCreateResized(uuid, width, format, subdir = '') {
} }
export { VALID_WIDTHS, SUPPORTED_FORMATS } export { VALID_WIDTHS, SUPPORTED_FORMATS }
/**
* Generate all resize widths in AVIF + WebP for eager processing.
* @param {string} uuid - UUID without extension
* @param {string} subdir - Subdirectory (e.g., 'reviews') or empty
* @param {string} originalPath - Full path to the original file
*/
export async function generateAllSizes(uuid, subdir, originalPath) {
const cacheSubdir = subdir ? subdir : ''
const cacheDir = path.join(CACHE_DIR, cacheSubdir)
await fs.promises.mkdir(cacheDir, { recursive: true })
const sharp = (await import('sharp')).default
for (const width of VALID_WIDTHS) {
for (const format of SUPPORTED_FORMATS) {
const cacheFileName = `${uuid}_w${width}.${format}`
const cachePath = path.join(CACHE_DIR, cacheSubdir, cacheFileName)
const pipeline = sharp(originalPath).resize(width, null, { withoutEnlargement: true })
const options = format === 'avif' ? { quality: 75, effort: 4 } : { quality: 80 }
await pipeline[format](options).toFile(cachePath)
}
}
}
/**
* Convert original file to WebP and delete the source file.
* @param {string} uuid - UUID without extension
* @param {string} subdir - Subdirectory (e.g., 'reviews') or empty
* @returns {string} New URL path like `/uploads/<uuid>.webp`
*/
export async function convertOriginalToWebp(uuid, subdir) {
const uploadsDir = path.join(process.cwd(), 'uploads')
const targetDir = subdir ? path.join(uploadsDir, subdir) : uploadsDir
const originalPath = await findOriginalFile(uuid, subdir)
if (!originalPath) {
throw new Error(`Original file not found for UUID: ${uuid}`)
}
const originalExt = path.extname(originalPath).toLowerCase()
const webpPath = path.join(targetDir, `${uuid}.webp`)
const sharp = (await import('sharp')).default
await sharp(originalPath).webp({ quality: 80 }).toFile(webpPath)
if (originalExt !== '.webp') {
await fs.promises.unlink(originalPath)
}
return subdir ? `/uploads/${subdir}/${uuid}.webp` : `/uploads/${uuid}.webp`
}