diff options
author | Ashelyn Rose <git@tempest.dev> | 2023-12-02 17:58:07 -0700 |
---|---|---|
committer | Ashelyn Rose <git@tempest.dev> | 2023-12-02 17:58:07 -0700 |
commit | 5289646abb229c9948dafda41d93a8bf85d56144 (patch) | |
tree | 6cdf0534a0ade477d12aa729f07d0edd806582da | |
parent | c74fad179379260dcf46edeae22c1f04ee842508 (diff) |
dedup generated images
-rw-r--r-- | components/Image/index.tsx | 115 |
1 files changed, 64 insertions, 51 deletions
diff --git a/components/Image/index.tsx b/components/Image/index.tsx index e7a393c..1fd139e 100644 --- a/components/Image/index.tsx +++ b/components/Image/index.tsx @@ -1,4 +1,5 @@ import { promises as fs } from 'fs' +import { createHash } from 'node:crypto' import path from 'path' import { ImgHTMLAttributes } from 'react' import sharp from 'sharp' @@ -24,77 +25,89 @@ type ImageProps = ImageAttrs interface ProcessedImage { hostPath: string, randPrefix: string, - widths: {[width: number] : string} + origSrc: string, + aspectRatio: number, } + async function ImageServer({src,...imgAttrs}: ImageProps) { - const srcImg = sharp(src) - const extension = path.extname(src) - const filename = path.basename(src, extension) - const randPrefix = (Math.random() + 1).toString(36).substring(7) const width = 'width' in imgAttrs ? imgAttrs.width : null const height = 'height' in imgAttrs ? imgAttrs.height : null - console.log(`processing ${src} for width ${width} and height ${height}`) - - // Make sure we can write to the dir - await fs.mkdir(outputDir, {recursive: true}) - - // Handle where we have an array of dimensions - if (Array.isArray(width) || Array.isArray(height)) { - const dimension = Array.isArray(width) ? 'width' : 'height' - const sizes = Array.isArray(width) ? width : height as number[] - const suffix = dimension.charAt(0) - - // Copy in original - fs.copyFile(src, path.join(outputDir, `${randPrefix}-${filename}${extension}`)) - - // Create resized copies - const srcSetLines = await Promise.all(sizes - .map(async size => { - const outputName = `${randPrefix}-${filename}-${size}${suffix}${extension}` - const outputPath = path.join(outputDir, outputName) - return srcImg - .resize({[dimension]: size}) - .toFile(outputPath) - .then(result => `${serverPath}${outputName} ${result.width}w`) - })) + // 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 ( <img - src={src} - srcSet={srcSetLines.join(',')} + src={_src} + srcSet={srcSet} {...imgAttrs} width={undefined} height={undefined} /> ) - } else { - const dimensions = { - width: typeof width === 'number' ? width : undefined, - height: typeof height === 'number' ? height : undefined - } - - const dimensionString = (() => { - if (dimensions.height && dimensions.height) - return `${dimensions.width}x${dimensions.height}` - if (dimensions.width) - return `${dimensions.width}w` - return `${dimensions.height}h` - })() - - const outputName = `${randPrefix}-${filename}-${dimensionString}${extension}` - const outputPath = path.join(outputDir, outputName) - const outputSrc = serverPath + outputName - await srcImg.resize(dimensions).toFile(outputPath) + }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 ( <img - src={outputSrc} + src={_src} + srcSet={srcSet} {...imgAttrs} - {...dimensions} + width={undefined} + height={undefined} /> ) + } else { + const {outputPath, resolvedWidth} = await processImage(src, {width, height}) + return <img src={outputPath} {...imgAttrs} height={undefined} width={resolvedWidth} /> + } +} + +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, } } |