Use Node zlib in a post-vite step so the Go server can serve negotiated variants without per-request compression cost. Co-authored-by: Cursor <cursoragent@cursor.com>
71 lines
2.4 KiB
JavaScript
71 lines
2.4 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Build-time precompress for web/dist: write sibling .br / .gz variants.
|
||
* Zero npm deps — uses Node zlib (available in node:22-bookworm web-builder).
|
||
*/
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { brotliCompressSync, constants as zlibConstants, gzipSync } from 'node:zlib';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
const distDir = path.resolve(__dirname, '..', 'dist');
|
||
const MIN_BYTES = 1024;
|
||
const COMPRESS_EXT = new Set(['.js', '.css', '.html', '.svg', '.json', '.txt', '.xml', '.map']);
|
||
const SKIP_EXT = new Set(['.woff', '.woff2', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.br', '.gz']);
|
||
|
||
function walk(dir, out = []) {
|
||
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
||
const full = path.join(dir, ent.name);
|
||
if (ent.isDirectory()) walk(full, out);
|
||
else out.push(full);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function shouldCompress(filePath) {
|
||
const ext = path.extname(filePath).toLowerCase();
|
||
if (SKIP_EXT.has(ext)) return false;
|
||
if (!COMPRESS_EXT.has(ext)) return false;
|
||
const st = fs.statSync(filePath);
|
||
return st.size >= MIN_BYTES;
|
||
}
|
||
|
||
function writeIfSmaller(sourcePath, variantPath, buf, label, originalSize) {
|
||
if (buf.length >= originalSize) {
|
||
if (fs.existsSync(variantPath)) fs.unlinkSync(variantPath);
|
||
return null;
|
||
}
|
||
fs.writeFileSync(variantPath, buf);
|
||
const pct = ((1 - buf.length / originalSize) * 100).toFixed(1);
|
||
console.log(
|
||
` ${label.padEnd(4)} ${path.relative(distDir, sourcePath)} ${originalSize} → ${buf.length} (−${pct}%)`,
|
||
);
|
||
return buf.length;
|
||
}
|
||
|
||
if (!fs.existsSync(distDir)) {
|
||
console.error(`precompress: missing ${distDir}; run vite build first`);
|
||
process.exit(1);
|
||
}
|
||
|
||
const files = walk(distDir).filter(shouldCompress);
|
||
console.log(`precompress: ${files.length} candidate(s) under ${distDir}`);
|
||
|
||
let brCount = 0;
|
||
let gzCount = 0;
|
||
for (const file of files) {
|
||
const raw = fs.readFileSync(file);
|
||
const br = brotliCompressSync(raw, {
|
||
params: {
|
||
[zlibConstants.BROTLI_PARAM_QUALITY]: 11,
|
||
[zlibConstants.BROTLI_PARAM_SIZE_HINT]: raw.length,
|
||
},
|
||
});
|
||
const gz = gzipSync(raw, { level: 9 });
|
||
if (writeIfSmaller(file, `${file}.br`, br, 'br', raw.length) != null) brCount += 1;
|
||
if (writeIfSmaller(file, `${file}.gz`, gz, 'gzip', raw.length) != null) gzCount += 1;
|
||
}
|
||
|
||
console.log(`precompress: wrote ${brCount} .br and ${gzCount} .gz`);
|