#!/usr/bin/env node /** * Asset-Komprimierungsskript für MYP Backend * Komprimiert CSS und JS-Dateien mit gzip für bessere Performance */ const fs = require('fs'); const path = require('path'); const zlib = require('zlib'); const { promisify } = require('util'); const gzip = promisify(zlib.gzip); const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); const stat = promisify(fs.stat); // Konfiguration const STATIC_DIR = path.join(__dirname, '..', 'static'); const COMPRESSION_LEVEL = 9; // Maximale Komprimierung // Dateierweiterungen die komprimiert werden sollen const COMPRESSIBLE_EXTENSIONS = ['.css', '.js', '.html', '.svg', '.json']; // Mindestgröße für Komprimierung (in Bytes) const MIN_SIZE_FOR_COMPRESSION = 1024; // 1KB /** * Komprimiert eine einzelne Datei */ async function compressFile(filePath) { try { const stats = await stat(filePath); // Überspringe kleine Dateien if (stats.size < MIN_SIZE_FOR_COMPRESSION) { console.log(`⏭️ Überspringe ${filePath} (zu klein: ${stats.size} bytes)`); return; } const data = await readFile(filePath); const compressed = await gzip(data, { level: COMPRESSION_LEVEL }); const gzPath = filePath + '.gz'; await writeFile(gzPath, compressed); const compressionRatio = ((stats.size - compressed.length) / stats.size * 100).toFixed(1); const originalSize = formatBytes(stats.size); const compressedSize = formatBytes(compressed.length); console.log(`✅ ${path.basename(filePath)}: ${originalSize} → ${compressedSize} (-${compressionRatio}%)`); } catch (error) { console.error(`❌ Fehler beim Komprimieren von ${filePath}:`, error.message); } } /** * Durchsucht Verzeichnis rekursiv nach komprimierbaren Dateien */ async function findCompressibleFiles(dir) { const files = []; try { const entries = await fs.promises.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { // Rekursiv in Unterverzeichnisse const subFiles = await findCompressibleFiles(fullPath); files.push(...subFiles); } else if (entry.isFile()) { const ext = path.extname(entry.name); // Überspringe bereits komprimierte Dateien if (entry.name.endsWith('.gz')) { continue; } // Prüfe ob Dateiart komprimierbar ist if (COMPRESSIBLE_EXTENSIONS.includes(ext)) { files.push(fullPath); } } } } catch (error) { console.warn(`⚠️ Kann Verzeichnis nicht lesen: ${dir} - ${error.message}`); } return files; } /** * Formatiert Byte-Größen lesbar */ function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } /** * Bereinigt alte .gz-Dateien */ async function cleanupOldGzFiles(dir) { try { const entries = await fs.promises.readdir(dir, { withFileTypes: true }); let cleaned = 0; for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { cleaned += await cleanupOldGzFiles(fullPath); } else if (entry.name.endsWith('.gz')) { const originalPath = fullPath.slice(0, -3); // Entferne .gz try { await stat(originalPath); // Original existiert, behalte .gz } catch (error) { // Original existiert nicht, lösche .gz await fs.promises.unlink(fullPath); console.log(`🗑️ Bereinigt: ${entry.name}`); cleaned++; } } } return cleaned; } catch (error) { console.warn(`⚠️ Fehler beim Bereinigen von ${dir}: ${error.message}`); return 0; } } /** * Hauptfunktion */ async function main() { console.log('🚀 MYP Asset-Komprimierung gestartet...\n'); const startTime = Date.now(); // Bereinige alte .gz-Dateien console.log('🧹 Bereinige alte komprimierte Dateien...'); const cleanedFiles = await cleanupOldGzFiles(STATIC_DIR); if (cleanedFiles > 0) { console.log(`✅ ${cleanedFiles} verwaiste .gz-Dateien bereinigt\n`); } else { console.log('✅ Keine Bereinigung erforderlich\n'); } // Finde alle komprimierbaren Dateien console.log('🔍 Suche nach komprimierbaren Dateien...'); const files = await findCompressibleFiles(STATIC_DIR); console.log(`📁 ${files.length} komprimierbare Dateien gefunden\n`); if (files.length === 0) { console.log('ℹ️ Keine Dateien zum Komprimieren gefunden.'); return; } // Komprimiere alle Dateien parallel (aber begrenzt) console.log('📦 Komprimiere Dateien...'); const BATCH_SIZE = 10; // Maximale parallele Verarbeitung let totalOriginalSize = 0; let totalCompressedSize = 0; let processedFiles = 0; for (let i = 0; i < files.length; i += BATCH_SIZE) { const batch = files.slice(i, i + BATCH_SIZE); const promises = batch.map(async (file) => { try { const stats = await stat(file); if (stats.size >= MIN_SIZE_FOR_COMPRESSION) { const data = await readFile(file); const compressed = await gzip(data, { level: COMPRESSION_LEVEL }); await writeFile(file + '.gz', compressed); totalOriginalSize += stats.size; totalCompressedSize += compressed.length; processedFiles++; return { file: path.basename(file), original: stats.size, compressed: compressed.length }; } } catch (error) { console.error(`❌ ${path.basename(file)}: ${error.message}`); return null; } }); const results = await Promise.all(promises); // Zeige Fortschritt für diese Batch results.filter(r => r).forEach(result => { const ratio = ((result.original - result.compressed) / result.original * 100).toFixed(1); console.log(`✅ ${result.file}: ${formatBytes(result.original)} → ${formatBytes(result.compressed)} (-${ratio}%)`); }); } const endTime = Date.now(); const duration = ((endTime - startTime) / 1000).toFixed(2); console.log('\n🎉 Komprimierung abgeschlossen!'); console.log(`📊 Statistiken:`); console.log(` • Verarbeitete Dateien: ${processedFiles}`); console.log(` • Originalgröße: ${formatBytes(totalOriginalSize)}`); console.log(` • Komprimierte Größe: ${formatBytes(totalCompressedSize)}`); if (totalOriginalSize > 0) { const totalSavings = ((totalOriginalSize - totalCompressedSize) / totalOriginalSize * 100).toFixed(1); console.log(` • Gesamt-Einsparung: ${formatBytes(totalOriginalSize - totalCompressedSize)} (-${totalSavings}%)`); } console.log(` • Dauer: ${duration}s`); console.log('\n💡 Tipp: Verwenden Sie gzip-komprimierte Dateien mit Flask-Compress für optimale Performance!'); } // Führe Skript nur aus wenn direkt aufgerufen if (require.main === module) { main().catch(error => { console.error('💥 Kritischer Fehler:', error); process.exit(1); }); } module.exports = { compressFile, findCompressibleFiles, cleanupOldGzFiles };