import { promises as fs } from 'fs' import { createHash } from 'node:crypto' import path from 'path' import { ImgHTMLAttributes } from 'react' import sharp from 'sharp' const outputDir = path.join(process.cwd(), '.next/static/images') const serverPath = '/_next/static/images/' interface ReqProps { src: string, alt: string, } type FixedSingleDimension = {width: number} | {height: number} type FixedBothDimension = {width: number, height: number} type MultiSize = {width: number[]} | {height: number[]} type SizeProps = FixedSingleDimension | FixedBothDimension | MultiSize type ImageAttrs = Omit, "width" | "height" | "src"> type ImageProps = ImageAttrs & SizeProps & ReqProps interface ProcessedImage { hostPath: string, randPrefix: string, origSrc: string, aspectRatio: number, } async function ImageServer({src,...imgAttrs}: ImageProps) { const width = 'width' in imgAttrs ? imgAttrs.width : null const height = 'height' in imgAttrs ? imgAttrs.height : null // Handle where we have an array of widths if (Array.isArray(width)) { let _src : string = src const srcSet = (await Promise.all(width.map(async width => { const {outputPath, origSrc} = await processImage(src, {width}) _src = origSrc return `${outputPath} ${width}w` }))).join(',') return ( ) }else if (Array.isArray(height)) { let _src : string = src const srcSet = (await Promise.all(height.map(async height => { const {outputPath, origSrc} = await processImage(src, {height}) _src = origSrc return `${outputPath} ${width}w` }))).join(',') return ( ) } else { const {outputPath, resolvedWidth} = await processImage(src, {width, height}) return } } async function processImage(sourcePath: string, {width, height} : {width?: number, height?: number}) : Promise<{outputPath: string, resolvedWidth: number, origSrc: string}> { await fs.mkdir(outputDir, {recursive: true}) const hostPath = path.join(process.cwd(), sourcePath) const srcImg = sharp(hostPath) const metadata = await srcImg.metadata() const extension = path.extname(hostPath) const filename = path.basename(hostPath, extension) const hashPrefix = createHash('sha256').update(hostPath).digest('hex').substr(0, 7) const origSrc = `${hashPrefix}-${filename}${extension}` const aspectRatio = metadata.width / metadata.height const resolvedWidth = width ? width : aspectRatio * height const outputName = `${hashPrefix}-${filename}-${resolvedWidth}w${extension}` const origCopyStat = await fs.stat(path.join(outputDir, origSrc)).catch(() => undefined) if (!origCopyStat?.isFile()) { console.log(`copying file ${hostPath}`) await fs.copyFile(hostPath, path.join(outputDir, origSrc)) } const outputStat = await fs.stat(path.join(outputDir, outputName)).catch(() => undefined) if (!outputStat?.isFile()) { console.log(`resizing file ${hostPath} for width ${resolvedWidth}`) await srcImg .resize({width: resolvedWidth}) .toFile(path.join(outputDir, outputName)) } return { outputPath: serverPath + outputName, resolvedWidth, origSrc, } } export default function ImageServerWrap(props: ImageProps) { return ( <> {/* @ts-expect-error Async Server Component */} ) }