summary refs log tree commit diff
path: root/components/Image/index.tsx
blob: e7a393c9e6096ecd8f80fdfe42cba605e1a4f4b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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} />
    </>
  )
}