-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathoptimize-images.ts
More file actions
268 lines (215 loc) · 8.91 KB
/
Copy pathoptimize-images.ts
File metadata and controls
268 lines (215 loc) · 8.91 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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
import fs from "fs-extra";
import path from "path";
import sharp from "sharp";
import { fileURLToPath } from "url";
// Definir __dirname manualmente (para compatibilidad con ES Modules)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 📂 Rutas de carpetas
const inputFolder = path.join(__dirname, "public");
const publicOptimizedFolder = path.join(inputFolder, "public-optimized");
const desktopFolder = path.join(publicOptimizedFolder, "desktop");
const mobileFolder = path.join(publicOptimizedFolder, "mobile");
// 📌 Configuración
const CONFIG = {
// Imágenes de fondo con tratamiento especial (alta resolución)
backgroundImages: new Set(["bg1.webp", "bg2.webp", "bg3.webp", "bg4.webp", "bg5.webp"]),
minSizeKB: 0,
// Configuración para imágenes de fondo
background: {
width: 1920,
quality: 85
},
// Configuración para imágenes normales
desktop: {
width: 1200,
quality: 80
},
mobile: {
width: 800,
quality: 70
},
// Extensiones de imagen a procesar
extensions: /\.(jpg|jpeg|png)$/i,
// Verificar si se ha pasado el argumento --force
forceReoptimize: process.argv.includes('--force')
};
/**
* Obtiene todas las imágenes dentro de un directorio y sus subdirectorios
* @param {string} dir - Directorio a explorar
* @returns {string[]} - Lista de rutas de imágenes
*/
const getAllImages = (dir: string): string[] => {
let results: string[] = [];
try {
const list = fs.readdirSync(dir);
for (const file of list) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
// Evitar procesar la carpeta public-optimized para evitar duplicación
if (path.basename(filePath) === 'public-optimized') {
console.log(`📁 Saltando carpeta de optimización: ${filePath}`);
continue;
}
// Recursión para buscar en subdirectorios
results = [...results, ...getAllImages(filePath)];
} else if (CONFIG.extensions.test(file)) {
// Solo incluir archivos con extensiones de imagen
results.push(filePath);
}
}
} catch (error) {
console.error(`❌ Error al explorar el directorio ${dir}:`, error.message);
}
return results;
};
/**
* Determina si una imagen necesita ser optimizada
* @param {string} originalPath - Ruta de la imagen original
* @param {string} optimizedDesktopPath - Ruta de la versión optimizada para escritorio
* @param {string} optimizedMobilePath - Ruta de la versión optimizada para móvil
* @returns {boolean} - True si necesita optimización, false si no
*/
const needsOptimization = (originalPath: string, optimizedDesktopPath: string, optimizedMobilePath: string): boolean => {
// Si se ha pasado --force, siempre optimizar
if (CONFIG.forceReoptimize) {
return true;
}
// Si alguno de los archivos optimizados no existe, necesita optimización
if (!fs.existsSync(optimizedDesktopPath) || !fs.existsSync(optimizedMobilePath)) {
return true;
}
try {
// Verificar fechas de modificación
const originalStat = fs.statSync(originalPath);
const desktopStat = fs.statSync(optimizedDesktopPath);
const mobileStat = fs.statSync(optimizedMobilePath);
// Si la imagen original es más reciente que alguna de las optimizadas, necesita optimización
return originalStat.mtime > desktopStat.mtime || originalStat.mtime > mobileStat.mtime;
} catch (error) {
// En caso de error, optimizar por precaución
console.error(`⚠️ Error al verificar fechas de modificación:`, error.message);
return true;
}
};
/**
* Determina la ruta de destino correcta para una imagen optimizada
* @param {string} sourcePath - Ruta completa de la imagen original
* @param {string} destFolder - Carpeta de destino (desktop o mobile)
* @returns {string} - Ruta de destino para la imagen optimizada
*/
const getOutputPath = (sourcePath: string, destFolder: string): string => {
// Obtener la ruta relativa al directorio 'public'
const relativePath = path.relative(inputFolder, sourcePath);
// Construir la ruta de destino y cambiar la extensión a .webp
return path.join(destFolder, relativePath).replace(CONFIG.extensions, '.webp');
};
/**
* Optimiza una imagen individual
* @param {string} filePath - Ruta de la imagen original
* @returns {Promise<boolean>} - True si se optimizó correctamente, false en caso contrario
*/
const optimizeImage = async (filePath: string): Promise<boolean> => {
const fileName = path.basename(filePath);
const stats = fs.statSync(filePath);
const sizeKB = stats.size / 1024;
// Construir rutas de salida
const outputDesktopPath = getOutputPath(filePath, desktopFolder);
const outputMobilePath = getOutputPath(filePath, mobileFolder);
// Crear directorios de salida si no existen
fs.ensureDirSync(path.dirname(outputDesktopPath));
fs.ensureDirSync(path.dirname(outputMobilePath));
// Verificar si la imagen necesita ser optimizada
if (!needsOptimization(filePath, outputDesktopPath, outputMobilePath)) {
console.log(`✅ Ya optimizada y actualizada: ${fileName}`);
return false; // No fue necesario optimizar
}
// Omitir imágenes muy pequeñas si está configurado
if (CONFIG.minSizeKB > 0 && sizeKB < CONFIG.minSizeKB) {
console.log(`⚠️ Omitiendo imagen pequeña: ${fileName} (${sizeKB.toFixed(2)} KB)`);
return false; // No se optimizó
}
console.log(`📷 Optimizando imagen: ${fileName} (${sizeKB.toFixed(2)} KB)`);
try {
// Optimización según el tipo de imagen
if (CONFIG.backgroundImages.has(fileName)) {
// Optimización para imágenes de fondo (solo versión desktop)
await sharp(filePath)
.resize({ width: CONFIG.background.width, withoutEnlargement: true })
.toFormat("webp", { quality: CONFIG.background.quality })
.toFile(outputDesktopPath);
// Para imágenes de fondo, copiamos la misma versión a mobile para mantener consistencia
fs.copyFileSync(outputDesktopPath, outputMobilePath);
} else {
// Optimización para versión desktop
await sharp(filePath)
.resize({ width: CONFIG.desktop.width, withoutEnlargement: true })
.toFormat("webp", { quality: CONFIG.desktop.quality })
.toFile(outputDesktopPath);
// Optimización para versión mobile
await sharp(filePath)
.resize({ width: CONFIG.mobile.width, withoutEnlargement: true })
.toFormat("webp", { quality: CONFIG.mobile.quality })
.toFile(outputMobilePath);
}
console.log(` ✅ Completado: ${fileName} → ${path.basename(outputDesktopPath)}`);
return true; // Optimización exitosa
} catch (error) {
console.error(` ❌ Error optimizando ${fileName}:`, error.message);
return false; // Error en la optimización
}
};
/**
* Función principal que inicia el proceso de optimización
*/
async function optimizeImages() {
console.log("\n🚀 INICIANDO PROCESO DE OPTIMIZACIÓN DE IMÁGENES\n");
console.log(`📂 Carpeta origen: ${inputFolder}`);
console.log(`📂 Carpeta destino: ${publicOptimizedFolder}`);
if (CONFIG.forceReoptimize) {
console.log("⚠️ Modo forzado: todas las imágenes serán reoptimizadas\n");
}
// Nota: No limpiamos todas las carpetas, solo marcamos para regeneración
if (CONFIG.forceReoptimize) {
console.log("⚠️ Modo forzado: todas las imágenes serán reoptimizadas aunque ya existan");
// NO usamos emptyDirSync que podría eliminar subcarpetas importantes
}
// Crear carpetas de salida si no existen
fs.ensureDirSync(desktopFolder);
fs.ensureDirSync(mobileFolder);
console.log("🔍 Buscando imágenes en `public/` y subcarpetas...");
// Obtener todas las imágenes
const imageFiles = getAllImages(inputFolder);
console.log(`🖼️ Se encontraron ${imageFiles.length} imágenes para procesar\n`);
// Contadores para estadísticas
let optimizedCount = 0;
let skippedCount = 0;
let errorCount = 0;
// Procesar cada imagen
for (const file of imageFiles) {
try {
const wasOptimized = await optimizeImage(file);
if (wasOptimized) {
optimizedCount++;
} else {
skippedCount++;
}
} catch (error) {
console.error(`❌ Error procesando imagen ${file}:`, error);
errorCount++;
}
}
// Mostrar resumen
console.log("\n✨ PROCESO COMPLETADO ✨");
console.log(`✅ Imágenes optimizadas: ${optimizedCount}`);
console.log(`⏭️ Imágenes omitidas: ${skippedCount}`);
if (errorCount > 0) {
console.log(`❌ Errores encontrados: ${errorCount}`);
}
console.log(`\n📂 Imágenes optimizadas disponibles en:`);
console.log(` 🖥️ ${desktopFolder} (Escritorio)`);
console.log(` 📱 ${mobileFolder} (Móvil)`);
}
// 🚀 Ejecutar optimización
optimizeImages();