const fs = require('fs'); const path = require('path'); const glob = require('glob'); const terser = require('terser'); const csso = require('csso'); const htmlmin = require('html-minifier'); // Configuration const config = { rootJs: { src: './*.js', exclude: './*.min.js', outputExt: '.min.js' }, js: { src: 'src/**/*.js', exclude: 'src/**/*.min.js', outputExt: '.min.js' }, css: { src: 'src/**/*.css', exclude: 'src/**/*.min.css', outputExt: '.min.css' }, json: { src: 'src/**/*.json' }, html: { src: 'src/**/*.html' }, images: { src: 'src/**/*.+(png|jpg|jpeg|gif|svg|ico)' } }; // Ensure directory exists function ensureDirectoryExistence(filePath) { const dirname = path.dirname(filePath); if (fs.existsSync(dirname)) return; ensureDirectoryExistence(dirname); fs.mkdirSync(dirname); } // Clean public directory function cleanPublicDir() { console.log('Cleaning public directory...'); if (fs.existsSync('public')) { fs.rmSync('public', { recursive: true, force: true }); } fs.mkdirSync('public'); console.log('✓ Public directory cleaned'); } // Create output path preserving directory structure function createOutputPath(file, baseDir, outputExt = null) { // Get relative path from the source root const relativePath = path.relative(baseDir, file); // Create public path with the same relative structure let outputPath = path.join('public', relativePath); // Apply output extension if provided if (outputExt) { outputPath = outputPath.replace(path.extname(outputPath), outputExt); } return outputPath; } // Minify JavaScript files async function minifyJS() { console.log('Minifying JavaScript files...'); // Minify root-level JS files (like app.js) const rootFiles = glob.sync(config.rootJs.src, { ignore: config.rootJs.exclude }); for (const file of rootFiles) { // Skip already minified files and module files if (file.endsWith('.min.js') || file === 'build.js') continue; const content = fs.readFileSync(file, 'utf8'); const result = await terser.minify(content, { compress: true, mangle: true }); const outputPath = createOutputPath(file, '.', config.rootJs.outputExt); ensureDirectoryExistence(outputPath); fs.writeFileSync(outputPath, result.code); console.log(`✓ Minified: ${file} -> ${outputPath}`); } // Minify JavaScript files in src directory const srcFiles = glob.sync(config.js.src, { ignore: config.js.exclude }); for (const file of srcFiles) { // Skip already minified files, type definition, and map files if (file.endsWith('.min.js') || file.endsWith('.d.ts') || file.endsWith('.map')) continue; const content = fs.readFileSync(file, 'utf8'); const result = await terser.minify(content, { compress: true, mangle: true }); const outputPath = createOutputPath(file, '.', config.js.outputExt); ensureDirectoryExistence(outputPath); fs.writeFileSync(outputPath, result.code); console.log(`✓ Minified: ${file} -> ${outputPath}`); } } // Minify CSS files function minifyCSS() { console.log('Minifying CSS files...'); const files = glob.sync(config.css.src, { ignore: config.css.exclude }); for (const file of files) { if (file.endsWith('.min.css')) continue; const content = fs.readFileSync(file, 'utf8'); const result = csso.minify(content); const outputPath = createOutputPath(file, '.', config.css.outputExt); ensureDirectoryExistence(outputPath); fs.writeFileSync(outputPath, result.css); console.log(`✓ Minified: ${file} -> ${outputPath}`); } } // Minify JSON files function minifyJSON() { console.log('Minifying JSON files...'); const files = glob.sync(config.json.src); for (const file of files) { const content = fs.readFileSync(file, 'utf8'); const jsonData = JSON.parse(content); const minified = JSON.stringify(jsonData); const outputPath = createOutputPath(file, '.'); ensureDirectoryExistence(outputPath); fs.writeFileSync(outputPath, minified); console.log(`✓ Minified: ${file} -> ${outputPath}`); } } // Minify HTML files and update references function minifyHTML() { console.log('Minifying HTML files...'); const files = glob.sync(config.html.src); for (const file of files) { let content = fs.readFileSync(file, 'utf8'); // Update JS references to minified versions, preserving directory structure content = content.replace(/src=["'](.+?)\.js["']/g, (match, p1) => { return `src="${p1}.min.js"`; }); // Update CSS references to minified versions, preserving directory structure content = content.replace(/href=["'](.+?)\.css["']/g, (match, p1) => { return `href="${p1}.min.css"`; }); // Minify HTML const minified = htmlmin.minify(content, { collapseWhitespace: true, removeComments: true, minifyJS: true, minifyCSS: true, removeRedundantAttributes: true, removeEmptyAttributes: true, removeOptionalTags: true }); const outputPath = createOutputPath(file, '.'); ensureDirectoryExistence(outputPath); fs.writeFileSync(outputPath, minified); console.log(`✓ Minified: ${file} -> ${outputPath}`); } } // Copy images function copyImages() { console.log('Copying images...'); const files = glob.sync(config.images.src); for (const file of files) { const outputPath = createOutputPath(file, '.'); ensureDirectoryExistence(outputPath); fs.copyFileSync(file, outputPath); console.log(`✓ Copied: ${file} -> ${outputPath}`); } } // Copy other files like README.md function copyMiscFiles() { console.log('Copying miscellaneous files...'); const filesToCopy = [ 'README.md', 'package.json', 'package-lock.json' ]; for (const file of filesToCopy) { if (fs.existsSync(file)) { const outputPath = path.join('public', file); fs.copyFileSync(file, outputPath); console.log(`✓ Copied: ${file} -> ${outputPath}`); } } } // Main function async function build() { console.log('Starting build process...'); cleanPublicDir(); await minifyJS(); minifyCSS(); minifyJSON(); minifyHTML(); copyImages(); copyMiscFiles(); console.log('Build completed successfully!'); } // Run the build build().catch(err => { console.error('Build failed:', err); process.exit(1); });