import { readdirSync, statSync } from "fs"; import { join, relative } from "path"; import sharp from "sharp"; import { fileURLToPath } from "url"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); const root_dir = join(__dirname, "../static"); const exceptions = ["assets/"]; /** * @type {{ * jpeg: sharp.JpegOptions, * webp: sharp.WebpOptions, * png: sharp.PngOptions, * gif: sharp.GifOptions, * avif: sharp.AvifOptions * }} */ const config = { jpeg: { quality: 100, }, webp: { lossless: true, }, png: { quality: 100, }, gif: { quality: 100, }, avif: { lossless: true, }, }; /** @type {sharp.ResizeOptions} */ const resize_config = { width: 1280, height: 1280, fit: sharp.fit.inside, withoutEnlargement: true, }; function* walk_directory(dir) { const files = readdirSync(dir); for (const file of files) { const pathToFile = join(dir, file); const isDirectory = statSync(pathToFile).isDirectory(); if (isDirectory) { yield* walk_directory(pathToFile); } else { yield pathToFile; } } } function is_image(file) { const extension = file.split(".").pop().toLowerCase(); return Object.keys(config).includes(extension); } function get_relative_path(file) { return relative(root_dir, file); } async function main() { for (const file of walk_directory(join(__dirname, "../static"))) { const relative_path = get_relative_path(file); const is_animated = file.endsWith(".gif"); if (!is_image(file)) continue; if (exceptions.some((exception) => relative_path.startsWith(exception))) continue; const image = sharp(file, { animated: is_animated, }); const size_before = (await image.toBuffer()).length; const meta = await image.metadata(); const buffer = await image[meta.format](config[meta.format]) .resize(resize_config) .toBuffer(); const size_after = buffer.length; if (size_after >= size_before) continue; console.log(relative_path); await sharp(buffer).toFile(file); } } await main();