computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
4.0 KiB
153 lines
4.0 KiB
import fs from 'node:fs/promises'; |
|
import path from 'node:path'; |
|
import { fileURLToPath } from 'node:url'; |
|
import sharp from 'sharp'; |
|
|
|
// ERROR: `__dirname` is not defined in ES module scope |
|
// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d |
|
const __filename = fileURLToPath(import.meta.url); |
|
const __dirname = path.dirname(__filename); |
|
|
|
const allowedFileExtensions = [ |
|
'.avif', |
|
'.gif', |
|
'.heif', |
|
'.jpeg', |
|
'.png', |
|
'.raw', |
|
'.tiff', |
|
'.webp', |
|
] as const; |
|
type AllowedFileExtension = (typeof allowedFileExtensions)[number]; |
|
|
|
const publicDir = path.join(__dirname, '../public'); |
|
const cacheFile = path.join(__dirname, '/compressed-images.json'); |
|
|
|
const KB_IN_BYTES = 1024; |
|
const COMPRESS_CONFIG = { |
|
avif: { |
|
chromaSubsampling: '4:4:4', |
|
effort: 9.0, |
|
}, |
|
gif: { |
|
effort: 10.0, |
|
}, |
|
jpeg: { |
|
chromaSubsampling: '4:4:4', |
|
mozjpeg: true, |
|
trellisQuantisation: true, |
|
overshootDeringing: true, |
|
optimiseScans: true, |
|
}, |
|
png: { |
|
compressionLevel: 9.0, |
|
palette: true, |
|
}, |
|
raw: {}, |
|
tiff: { |
|
compression: 'lzw', |
|
}, |
|
webp: { |
|
effort: 6.0, |
|
}, |
|
}; |
|
|
|
(async () => { |
|
let cache: string[] = []; |
|
const isCacheFileExists = await fs |
|
.access(cacheFile) |
|
.then(() => true) |
|
.catch(() => false); |
|
|
|
if (isCacheFileExists) { |
|
const cacheFileContent = await fs.readFile(cacheFile, 'utf8'); |
|
cache = JSON.parse(cacheFileContent); |
|
} |
|
|
|
const images = await recursiveGetImages(publicDir); |
|
for (const image of images) { |
|
const extname = path.extname(image).toLowerCase() as AllowedFileExtension; |
|
if ( |
|
!allowedFileExtensions.includes(extname) || |
|
image.includes('node_modules') || |
|
image.includes('.astro') || |
|
image.includes('.vscode') || |
|
image.includes('.git') |
|
) { |
|
continue; |
|
} |
|
|
|
const stats = await fs.stat(image); |
|
const relativeImagePath = path.relative(path.join(__dirname, '..'), image); |
|
if (cache.includes(relativeImagePath)) { |
|
continue; |
|
} |
|
|
|
const prevSize = stats.size / KB_IN_BYTES; |
|
|
|
let imageBuffer: Buffer | undefined; |
|
switch (extname) { |
|
case '.avif': |
|
imageBuffer = await sharp(image).avif(COMPRESS_CONFIG.avif).toBuffer(); |
|
break; |
|
case '.heif': |
|
imageBuffer = await sharp(image).heif().toBuffer(); |
|
break; |
|
case '.jpeg': |
|
imageBuffer = await sharp(image).jpeg(COMPRESS_CONFIG.jpeg).toBuffer(); |
|
break; |
|
case '.png': |
|
imageBuffer = await sharp(image).png(COMPRESS_CONFIG.png).toBuffer(); |
|
break; |
|
case '.raw': |
|
imageBuffer = await sharp(image).raw().toBuffer(); |
|
break; |
|
case '.tiff': |
|
imageBuffer = await sharp(image).tiff(COMPRESS_CONFIG.tiff).toBuffer(); |
|
break; |
|
case '.webp': |
|
imageBuffer = await sharp(image).webp(COMPRESS_CONFIG.webp).toBuffer(); |
|
break; |
|
case '.gif': |
|
continue; |
|
} |
|
|
|
if (!imageBuffer) { |
|
console.error(`❌ ${image} Compressing failed!`); |
|
continue; |
|
} |
|
|
|
const newSize = imageBuffer.length / KB_IN_BYTES; |
|
const diff = prevSize - newSize; |
|
if (diff <= 0) { |
|
console.log(`📦 Skipped ${relativeImagePath}`); |
|
continue; |
|
} |
|
|
|
const diffPercent = ((diff / prevSize) * 100).toFixed(2); |
|
console.log( |
|
`📦 Reduced ${prevSize.toFixed(2)}KB → ${newSize.toFixed(2)}KB (${diff.toFixed(2)}KB, ${diffPercent}%) for ${relativeImagePath}`, |
|
); |
|
|
|
await fs.writeFile(image, imageBuffer); |
|
cache.push(relativeImagePath); |
|
|
|
// So that we don't lose the cache if the script crashes |
|
await fs.writeFile(cacheFile, JSON.stringify(cache, null, 2), 'utf8'); |
|
} |
|
|
|
await fs.writeFile(cacheFile, JSON.stringify(cache, null, 2), 'utf8'); |
|
})(); |
|
|
|
async function recursiveGetImages(dir: string): Promise<string[]> { |
|
const subdirs = await fs.readdir(dir, { withFileTypes: true }); |
|
|
|
const files = await Promise.all( |
|
subdirs.map((dirent) => { |
|
const res = path.resolve(dir, dirent.name); |
|
return dirent.isDirectory() ? recursiveGetImages(res) : res; |
|
}), |
|
); |
|
|
|
return Array.prototype.concat(...files); |
|
}
|
|
|