238 lines
8.1 KiB
JavaScript
238 lines
8.1 KiB
JavaScript
#!/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 };
|