feat: add hero image processing and API for serving variants
- Introduced a new script `prepare-hero-variants.mjs` to generate responsive hero image variants in WebP format. - Added a fallback image `hero_fallback.png` for each variant. - Created an API endpoint `hero-images.get.js` to retrieve available hero image variants and their fallback images. - Implemented directory and file checks to ensure the existence of required images before serving.
This commit is contained in:
87
server/api/hero-images.get.js
Normal file
87
server/api/hero-images.get.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const HERO_ROOT_CANDIDATES = [
|
||||
path.join(process.cwd(), 'public', 'images', 'hero'),
|
||||
path.join(process.cwd(), '.output', 'public', 'images', 'hero'),
|
||||
path.join(process.cwd(), '..', 'public', 'images', 'hero'),
|
||||
path.join(process.cwd(), '..', '.output', 'public', 'images', 'hero')
|
||||
]
|
||||
|
||||
const FALLBACK_FILE_CANDIDATES = [
|
||||
'hero_fallback.webp',
|
||||
'hero_fallback.jpg',
|
||||
'hero_fallback.jpeg',
|
||||
'hero_fallback.png'
|
||||
]
|
||||
|
||||
async function findExistingDir(paths) {
|
||||
for (const candidate of paths) {
|
||||
try {
|
||||
const stats = await fs.stat(candidate)
|
||||
if (stats.isDirectory()) return candidate
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function fileExists(filePath) {
|
||||
try {
|
||||
const stats = await fs.stat(filePath)
|
||||
return stats.isFile()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function listHeroVariants(heroRoot) {
|
||||
const dirEntries = await fs.readdir(heroRoot, { withFileTypes: true })
|
||||
const variants = []
|
||||
|
||||
for (const entry of dirEntries) {
|
||||
if (!entry.isDirectory()) continue
|
||||
|
||||
const key = entry.name
|
||||
const variantDir = path.join(heroRoot, key)
|
||||
const mobileFile = 'hero_960.webp'
|
||||
const desktopFile = 'hero_1600.webp'
|
||||
|
||||
const mobilePath = path.join(variantDir, mobileFile)
|
||||
const desktopPath = path.join(variantDir, desktopFile)
|
||||
|
||||
if (!(await fileExists(mobilePath)) || !(await fileExists(desktopPath))) {
|
||||
continue
|
||||
}
|
||||
|
||||
let fallbackFile = desktopFile
|
||||
for (const candidate of FALLBACK_FILE_CANDIDATES) {
|
||||
if (await fileExists(path.join(variantDir, candidate))) {
|
||||
fallbackFile = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
variants.push({
|
||||
key,
|
||||
mobileWebp: `/images/hero/${key}/${mobileFile}`,
|
||||
desktopWebp: `/images/hero/${key}/${desktopFile}`,
|
||||
fallback: `/images/hero/${key}/${fallbackFile}`
|
||||
})
|
||||
}
|
||||
|
||||
return variants.sort((a, b) => a.key.localeCompare(b.key, 'de'))
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const heroRoot = await findExistingDir(HERO_ROOT_CANDIDATES)
|
||||
if (!heroRoot) {
|
||||
return { variants: [] }
|
||||
}
|
||||
|
||||
const variants = await listHeroVariants(heroRoot)
|
||||
setHeader(event, 'Cache-Control', 'public, max-age=300, stale-while-revalidate=600')
|
||||
|
||||
return { variants }
|
||||
})
|
||||
Reference in New Issue
Block a user