Files
Projektarbeit-MYP/backend/scripts/compress-assets.js

238 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 };