-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsharp-api.ts
More file actions
159 lines (131 loc) · 4.5 KB
/
sharp-api.ts
File metadata and controls
159 lines (131 loc) · 4.5 KB
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { readFile, unlink, writeFile } from 'node:fs/promises'
import fs from 'node:fs/promises'
import path from 'node:path'
import glob from 'fast-glob'
import sharp from 'sharp'
export const SHARP_OPTIONS: {
png: sharp.PngOptions
jpeg: sharp.JpegOptions
webp: sharp.WebpOptions
avif: sharp.AvifOptions
} = {
png: {},
jpeg: {},
webp: {},
avif: {
quality: 65,
},
}
export type SharpOptionType = keyof typeof SHARP_OPTIONS
export const SHARP_OPTIONS_TYPE_MAPPER = {
png: 'png',
jpg: 'jpeg',
jpeg: 'jpeg',
webp: 'webp',
avif: 'avif',
} as const satisfies { [key: string]: SharpOptionType }
export type SharpFileType = keyof typeof SHARP_OPTIONS_TYPE_MAPPER
const CONFIG = {
imageGlobPattern: 'public/images/**/*.{png,jpg,jpeg,webp}',
mdxGlobPattern: 'src/content/post/**/*.mdx',
ignoreList: ['og.png'],
}
export type ProcessedResult = {
name: string
path: string
beforeSize: number
afterSize: number
percentChange: number
convertedToAvif?: boolean
avifPath?: string
}
const updateMdxReferences = async (oldName: string, newName: string) => {
const mdxFiles = await glob(CONFIG.mdxGlobPattern)
let updatedFiles = 0
for (const mdxPath of mdxFiles) {
try {
const content = await readFile(mdxPath, 'utf-8')
const updatedContent = content.replaceAll(oldName, newName)
if (content !== updatedContent) {
await writeFile(mdxPath, updatedContent, 'utf-8')
console.log(`::✧:: Updated references in ${mdxPath}`)
updatedFiles++
}
} catch (error) {
console.log('::error:: Error updating MDX file:', mdxPath, error)
}
}
return updatedFiles
}
export const sharpImages = async () => {
console.log('::✧:: start sharp images')
const sharpedImageList: ProcessedResult[] = []
const unSharpedImageList: ProcessedResult[] = []
let mdxUpdates = 0
const files = await glob(CONFIG.imageGlobPattern)
for (const filePath of files) {
try {
const filename = path.basename(filePath)
if (CONFIG.ignoreList.includes(filename)) {
console.log(`::✧:: Skipping ignored file ${filename}`)
continue
}
console.log('::✧:: Processing', filePath)
const fileType = path.extname(filePath).slice(1) as SharpFileType
const sharpOptionType = SHARP_OPTIONS_TYPE_MAPPER[fileType]
const sharpOption = SHARP_OPTIONS[sharpOptionType]
const sharpedFilePath = filePath.replace(`.${fileType}`, `.sharp.${fileType}`)
await sharp(filePath)[sharpOptionType](sharpOption).toFile(sharpedFilePath)
const beforeStats = await fs.stat(filePath)
const afterStats = await fs.stat(sharpedFilePath)
const processedResult: ProcessedResult = {
name: filename,
path: filePath,
beforeSize: beforeStats.size,
afterSize: afterStats.size,
percentChange: +((1 - afterStats.size / beforeStats.size) * 100).toFixed(2),
}
if (processedResult.percentChange > 1) {
await fs.writeFile(filePath, await fs.readFile(sharpedFilePath))
const avifPath = filePath.replace(`.${fileType}`, '.avif')
await sharp(filePath).avif(SHARP_OPTIONS.avif).toFile(avifPath)
const avifStats = await fs.stat(avifPath)
if (avifStats.size < afterStats.size) {
processedResult.convertedToAvif = true
processedResult.avifPath = avifPath
const avifFilename = path.basename(avifPath)
mdxUpdates += await updateMdxReferences(filename, avifFilename)
await unlink(filePath)
} else {
await unlink(avifPath)
}
sharpedImageList.push(processedResult)
} else {
unSharpedImageList.push(processedResult)
}
await unlink(sharpedFilePath)
} catch (error) {
console.log('::error::', error)
}
}
const sharpBeforeSize = sharpedImageList.reduce((acc, image) => acc + image.beforeSize, 0)
const sharpAfterSize = sharpedImageList.reduce((acc, image) => acc + image.afterSize, 0)
const metrics = {
totalFiles: sharpedImageList.length + unSharpedImageList.length,
sharpFiles: sharpedImageList.length,
sharpBeforeSize,
sharpAfterSize,
savedBytes: sharpBeforeSize - sharpAfterSize,
savedPercent:
sharpBeforeSize > 0 ? +((1 - sharpAfterSize / sharpBeforeSize) * 100).toFixed(2) : 0,
mdxUpdates,
avifConverted: sharpedImageList.filter((img) => img.convertedToAvif).length,
}
console.log('✧ sharp metrics')
console.log(metrics)
return {
sharpedImageList,
unSharpedImageList,
metrics,
}
}