diff --git a/build-simple.js b/build-simple.js index c9d0945..27da153 100644 --- a/build-simple.js +++ b/build-simple.js @@ -1,6 +1,7 @@ const fs = require("fs"); const path = require("path"); const { execSync } = require("child_process"); +const { minify } = require("terser"); // Build configuration const PROVIDERS_DIR = "./providers"; @@ -91,6 +92,101 @@ class ProviderBuilder { } } + /** + * Minify all JavaScript files in the dist directory + */ + async minifyFiles() { + const keepConsole = process.env.KEEP_CONSOLE === "true"; + log.build( + `Minifying JavaScript files... ${ + keepConsole ? "(keeping console logs)" : "(removing console logs)" + }` + ); + + const minifyFile = async (filePath) => { + try { + const code = fs.readFileSync(filePath, "utf8"); + const result = await minify(code, { + compress: { + drop_console: !keepConsole, // Remove console logs unless KEEP_CONSOLE=true + drop_debugger: true, + pure_funcs: keepConsole + ? ["console.debug"] + : [ + "console.debug", + "console.log", + "console.info", + "console.warn", + ], + }, + mangle: { + keep_fnames: false, // Mangle function names for better compression + }, + format: { + comments: false, // Remove comments + }, + }); + + if (result.code) { + fs.writeFileSync(filePath, result.code); + return true; + } else { + log.warning(`Failed to minify ${filePath}: No output code`); + return false; + } + } catch (error) { + log.error(`Error minifying ${filePath}: ${error.message}`); + return false; + } + }; + + const findJsFiles = (dir) => { + const files = []; + const items = fs.readdirSync(dir, { withFileTypes: true }); + + for (const item of items) { + const fullPath = path.join(dir, item.name); + if (item.isDirectory()) { + files.push(...findJsFiles(fullPath)); + } else if (item.isFile() && item.name.endsWith(".js")) { + files.push(fullPath); + } + } + + return files; + }; + + const jsFiles = findJsFiles(DIST_DIR); + let minifiedCount = 0; + let totalSizeBefore = 0; + let totalSizeAfter = 0; + + for (const filePath of jsFiles) { + const statsBefore = fs.statSync(filePath); + totalSizeBefore += statsBefore.size; + + const success = await minifyFile(filePath); + if (success) { + const statsAfter = fs.statSync(filePath); + totalSizeAfter += statsAfter.size; + minifiedCount++; + } + } + + const compressionRatio = + totalSizeBefore > 0 + ? ( + ((totalSizeBefore - totalSizeAfter) / totalSizeBefore) * + 100 + ).toFixed(1) + : 0; + + log.success( + `Minified ${minifiedCount}/${jsFiles.length} files. ` + + `Size reduced by ${compressionRatio}% (${totalSizeBefore} → ${totalSizeAfter} bytes)` + ); + } + /** * Organize compiled files by provider */ @@ -137,7 +233,7 @@ class ProviderBuilder { /** * Build everything */ - build() { + async build() { const isWatchMode = process.env.NODE_ENV === "development"; if (isWatchMode) { @@ -163,6 +259,13 @@ class ProviderBuilder { this.organizeFiles(); + // Add minification step (skip if SKIP_MINIFY is set) + if (!process.env.SKIP_MINIFY) { + await this.minifyFiles(); + } else { + log.info("Skipping minification (SKIP_MINIFY=true)"); + } + const buildTime = Date.now() - this.startTime; log.success(`Build completed in ${buildTime}ms`); @@ -178,4 +281,7 @@ class ProviderBuilder { // Run the build const builder = new ProviderBuilder(); -builder.build(); +builder.build().catch((error) => { + console.error("Build failed:", error); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index 2d2311e..bdf6767 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,8 +20,10 @@ "@types/node": "^24.0.1", "concurrently": "^8.2.2", "cors": "^2.8.5", + "cross-env": "^7.0.3", "express": "^4.21.2", "nodemon": "^3.1.10", + "terser": "^5.43.1", "typescript": "^5.8.3" } }, @@ -73,6 +75,56 @@ "node": ">=12" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@types/cheerio": { "version": "0.22.35", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz", @@ -107,6 +159,19 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -257,6 +322,13 @@ "node": ">=8" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -544,6 +616,13 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -659,6 +738,25 @@ "node": ">= 0.10" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2177,6 +2275,27 @@ "node": ">=10" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -2302,6 +2421,25 @@ "node": ">=4" } }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index 32f10ac..6d4a91e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test:quick": "npm run build && node quick-test.js", "test:ui": "npm run test:interactive", "build": "node build-simple.js", + "build:dev": "cross-env SKIP_MINIFY=true node build-simple.js", "dev": "node dev-server.js", "dev:build": "npm run build && npm run dev", "watch": "nodemon", @@ -27,8 +28,10 @@ "@types/node": "^24.0.1", "concurrently": "^8.2.2", "cors": "^2.8.5", + "cross-env": "^7.0.3", "express": "^4.21.2", "nodemon": "^3.1.10", + "terser": "^5.43.1", "typescript": "^5.8.3" }, "dependencies": {