94 lines
2.2 KiB
JavaScript
94 lines
2.2 KiB
JavaScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import sharp from "sharp";
|
|
|
|
const ROOT = process.cwd();
|
|
const TARGET_DIRS = ["public", "images"];
|
|
const SKIP_DIRS = new Set(["node_modules", ".next", "out", ".git"]);
|
|
const EXTENSIONS = new Set([".jpg", ".jpeg", ".png"]);
|
|
|
|
async function exists(p) {
|
|
try {
|
|
await fs.access(p);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function walk(dir, files = []) {
|
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
await walk(fullPath, files);
|
|
} else if (entry.isFile()) {
|
|
const ext = path.extname(entry.name).toLowerCase();
|
|
if (EXTENSIONS.has(ext)) {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
async function optimizeImage(filePath) {
|
|
const ext = path.extname(filePath).toLowerCase();
|
|
const input = sharp(filePath);
|
|
const tempPath = `${filePath}.tmp`;
|
|
|
|
if (ext === ".png") {
|
|
await input.png({ compressionLevel: 9, adaptiveFiltering: true }).toFile(tempPath);
|
|
} else {
|
|
await input.jpeg({ quality: 82, mozjpeg: true }).toFile(tempPath);
|
|
}
|
|
|
|
const [origStat, tempStat] = await Promise.all([
|
|
fs.stat(filePath),
|
|
fs.stat(tempPath),
|
|
]);
|
|
|
|
if (tempStat.size <= origStat.size) {
|
|
await fs.rename(tempPath, filePath);
|
|
} else {
|
|
await fs.unlink(tempPath);
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const targets = [];
|
|
for (const dir of TARGET_DIRS) {
|
|
const full = path.join(ROOT, dir);
|
|
if (await exists(full)) targets.push(full);
|
|
}
|
|
|
|
if (targets.length === 0) {
|
|
console.log("No image directories found (public/, images/).");
|
|
return;
|
|
}
|
|
|
|
const files = [];
|
|
for (const dir of targets) {
|
|
await walk(dir, files);
|
|
}
|
|
|
|
if (files.length === 0) {
|
|
console.log("No .jpg/.jpeg/.png files found.");
|
|
return;
|
|
}
|
|
|
|
console.log(`Optimizing ${files.length} images...`);
|
|
for (const file of files) {
|
|
try {
|
|
await optimizeImage(file);
|
|
} catch (err) {
|
|
console.warn(`Skipping ${file}: ${err?.message ?? err}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
}); |