You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
10 months ago
|
import { promises as fs } from 'fs'
|
||
|
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<ImgHTMLAttributes<HTMLImageElement>, "width" | "height" | "src">
|
||
|
|
||
|
type ImageProps = ImageAttrs
|
||
|
& SizeProps
|
||
|
& ReqProps
|
||
|
|
||
|
interface ProcessedImage {
|
||
|
hostPath: string,
|
||
|
randPrefix: string,
|
||
|
widths: {[width: number] : string}
|
||
|
}
|
||
|
|
||
|
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`)
|
||
|
}))
|
||
|
|
||
|
return (
|
||
|
<img
|
||
|
src={src}
|
||
|
srcSet={srcSetLines.join(',')}
|
||
|
{...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)
|
||
|
|
||
|
return (
|
||
|
<img
|
||
|
src={outputSrc}
|
||
|
{...imgAttrs}
|
||
|
{...dimensions}
|
||
|
/>
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default function ImageServerWrap(props: ImageProps) {
|
||
|
return (
|
||
|
<>
|
||
|
{/* @ts-expect-error Async Server Component */}
|
||
|
<ImageServer {...props} />
|
||
|
</>
|
||
|
)
|
||
|
}
|