#!/usr/bin/env node /** * Build and Minification Script for Spotify Web Player * Optimizes JavaScript and CSS for production deployment */ const fs = require('fs'); const path = require('path'); const { promisify } = require('util'); // Promisify file system operations const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); const readdir = promisify(fs.readdir); const stat = promisify(fs.stat); const mkdir = promisify(fs.mkdir); class BuildMinifier { constructor() { this.sourceDir = path.join(__dirname, '..', 'public'); this.outputDir = path.join(__dirname, '..', 'public', 'dist'); this.stats = { filesProcessed: 0, originalSize: 0, compressedSize: 0, compressionRatio: 0 }; } /** * Run the complete build process */ async build() { console.log('š Starting build process...\n'); try { // Ensure output directory exists await this.ensureOutputDir(); // Build JavaScript bundle await this.buildJavaScript(); // Build CSS bundle await this.buildCSS(); // Copy critical assets await this.copyAssets(); // Generate manifest await this.generateManifest(); // Display build statistics this.displayStats(); console.log('\nā Build completed successfully!'); } catch (error) { console.error('\nā Build failed:', error.message); process.exit(1); } } /** * Ensure output directory exists */ async ensureOutputDir() { try { await mkdir(this.outputDir, { recursive: true }); console.log('š Output directory created'); } catch (error) { if (error.code !== 'EEXIST') { throw error; } } } /** * Build and minify JavaScript files */ async buildJavaScript() { console.log('š¦ Building JavaScript bundle...'); // Define the order of JavaScript files for proper dependency loading const jsFiles = [ // Core services first 'js/services/StateManager.js', 'js/services/DebounceService.js', 'js/services/CacheService.js', 'js/services/SecurityService.js', 'js/services/DOMSanitizer.js', // Enhanced services 'js/services/AccessibilityService.js', 'js/services/SkeletonService.js', 'js/services/OfflineService.js', 'js/services/TouchGestureService.js', 'js/services/ErrorRecoveryService.js', 'js/services/ErrorBoundaryService.js', 'js/services/SpotifyAPIService.js', 'js/services/StateChangeDetectionService.js', // Components 'js/components/VirtualScrollComponent.js', 'js/components/PlaylistComponent.js', 'js/components/PlayerComponent.js', 'js/components/DeviceComponent.js', 'js/components/NavigationComponent.js', // Main application 'js/SpotifyWebPlayer.js' ]; let combinedJS = ''; let originalSize = 0; // Add header comment combinedJS += `/*!\n * Spotify Web Player - Production Bundle\n * Built on ${new Date().toISOString()}\n */\n\n`; // Combine all JavaScript files for (const file of jsFiles) { const filePath = path.join(this.sourceDir, file); try { const content = await readFile(filePath, 'utf8'); const fileSize = Buffer.byteLength(content, 'utf8'); originalSize += fileSize; // Add file separator comment combinedJS += `\n/* === ${file} === */\n`; combinedJS += content; combinedJS += '\n'; console.log(` ā ${file} (${this.formatBytes(fileSize)})`); } catch (error) { console.warn(` ā ļø Warning: Could not read ${file}: ${error.message}`); } } // Simple minification (remove comments and extra whitespace) const minifiedJS = this.minifyJavaScript(combinedJS); const compressedSize = Buffer.byteLength(minifiedJS, 'utf8'); // Write minified bundle const outputPath = path.join(this.outputDir, 'app.min.js'); await writeFile(outputPath, minifiedJS, 'utf8'); // Update stats this.stats.originalSize += originalSize; this.stats.compressedSize += compressedSize; this.stats.filesProcessed += jsFiles.length; console.log(` š Bundle created: app.min.js (${this.formatBytes(compressedSize)})`); console.log(` š¾ Compression: ${this.formatBytes(originalSize)} ā ${this.formatBytes(compressedSize)} (${Math.round((1 - compressedSize/originalSize) * 100)}% reduction)\n`); } /** * Build and minify CSS files */ async buildCSS() { console.log('šØ Building CSS bundle...'); const cssFiles = [ 'css/styles.css' ]; let combinedCSS = ''; let originalSize = 0; // Add header comment combinedCSS += `/*!\n * Spotify Web Player - Styles\n * Built on ${new Date().toISOString()}\n */\n\n`; // Combine all CSS files for (const file of cssFiles) { const filePath = path.join(this.sourceDir, file); try { const content = await readFile(filePath, 'utf8'); const fileSize = Buffer.byteLength(content, 'utf8'); originalSize += fileSize; combinedCSS += content; combinedCSS += '\n'; console.log(` ā ${file} (${this.formatBytes(fileSize)})`); } catch (error) { console.warn(` ā ļø Warning: Could not read ${file}: ${error.message}`); } } // Simple CSS minification const minifiedCSS = this.minifyCSS(combinedCSS); const compressedSize = Buffer.byteLength(minifiedCSS, 'utf8'); // Write minified bundle const outputPath = path.join(this.outputDir, 'styles.min.css'); await writeFile(outputPath, minifiedCSS, 'utf8'); // Update stats this.stats.originalSize += originalSize; this.stats.compressedSize += compressedSize; this.stats.filesProcessed += cssFiles.length; console.log(` š Bundle created: styles.min.css (${this.formatBytes(compressedSize)})`); console.log(` š¾ Compression: ${this.formatBytes(originalSize)} ā ${this.formatBytes(compressedSize)} (${Math.round((1 - compressedSize/originalSize) * 100)}% reduction)\n`); } /** * Copy critical assets */ async copyAssets() { console.log('š Copying critical assets...'); // Copy service worker (no minification needed) const swSource = path.join(this.sourceDir, 'sw.js'); const swDest = path.join(this.outputDir, 'sw.js'); try { const swContent = await readFile(swSource, 'utf8'); await writeFile(swDest, swContent, 'utf8'); console.log(' ā Service Worker copied'); } catch (error) { console.warn(' ā ļø Warning: Could not copy service worker:', error.message); } // Create production HTML file await this.createProductionHTML(); } /** * Create production HTML file with minified assets */ async createProductionHTML() { const htmlTemplate = `